Index: src/orca/scripts/toolkits/Gecko/constants.py
===================================================================
--- src/orca/scripts/toolkits/Gecko/constants.py	(revision 3927)
+++ src/orca/scripts/toolkits/Gecko/constants.py	(working copy)
@@ -1,5 +1,3 @@
-import pyatspi
-
 # If True, it tells us to take over caret navigation.  This is something
 # that can be set in user-settings.py:
 #
@@ -70,16 +68,5 @@
 # contain to be considered a match in go to next/prev large object
 largeObjectTextLength = 75
 
-# Roles that represent a logical chunk of information in a document
-#
-OBJECT_ROLES = [pyatspi.ROLE_HEADING,
-                pyatspi.ROLE_PARAGRAPH,
-                pyatspi.ROLE_TABLE,
-                pyatspi.ROLE_TABLE_CELL,
-                pyatspi.ROLE_TEXT,
-                pyatspi.ROLE_SECTION,
-                pyatspi.ROLE_DOCUMENT_FRAME,
-                pyatspi.ROLE_AUTOCOMPLETE]
-
 ARIA_LANDMARKS = ["banner", "contentinfo", "definition", "main", "navigation",
                   "note", "search", "secondary", "seealso"]
Index: src/orca/scripts/toolkits/Gecko/Makefile.am
===================================================================
--- src/orca/scripts/toolkits/Gecko/Makefile.am	(revision 3927)
+++ src/orca/scripts/toolkits/Gecko/Makefile.am	(working copy)
@@ -7,6 +7,7 @@
 	__init__.py \
 	script.py \
 	speech_generator.py \
+	structural_navigation.py \
 	where_am_i.py
 
 orca_pythondir=$(pyexecdir)/orca/scripts/toolkits/Gecko
Index: src/orca/scripts/toolkits/Gecko/script.py
===================================================================
--- src/orca/scripts/toolkits/Gecko/script.py	(revision 3927)
+++ src/orca/scripts/toolkits/Gecko/script.py	(working copy)
@@ -65,10 +65,11 @@
 from speech_generator import SpeechGenerator
 from where_am_i import GeckoWhereAmI
 from bookmarks import GeckoBookmarks
+from structural_navigation import GeckoStructuralNavigation
 
 from orca.orca_i18n import _
-from orca.orca_i18n import ngettext # for ngettext support
 
+
 ########################################################################
 #                                                                      #
 # Script                                                               #
@@ -86,7 +87,6 @@
 
     def __init__(self, app):
         default.Script.__init__(self, app)
-
         # Initialize variables to make pylint happy.
         #
         self.arrowToLineBeginningCheckButton = None
@@ -118,57 +118,6 @@
              Script.goTopOfFile,
              Script.goBottomOfFile]
 
-        # _structuralNavigationFunctions are functions that represent
-        # more complex navigation functions (e.g., moving by heading,
-        # large object, etc.).
-        #
-        self._structuralNavigationFunctions = \
-            [Script.goNextHeading,
-             Script.goPreviousHeading,
-             Script.goNextHeading1,
-             Script.goPreviousHeading1,
-             Script.goNextHeading2,
-             Script.goPreviousHeading2,
-             Script.goNextHeading3,
-             Script.goPreviousHeading3,
-             Script.goNextHeading4,
-             Script.goPreviousHeading4,
-             Script.goNextHeading5,
-             Script.goPreviousHeading5,
-             Script.goNextHeading6,
-             Script.goPreviousHeading6,
-             Script.goNextChunk,
-             Script.goPreviousChunk,
-             Script.goNextLandmark,
-             Script.goPreviousLandmark,
-             Script.goNextList,
-             Script.goPreviousList,
-             Script.goNextListItem,
-             Script.goPreviousListItem,
-             Script.goNextUnvisitedLink,
-             Script.goPreviousUnvisitedLink,
-             Script.goNextVisitedLink,
-             Script.goPreviousVisitedLink,
-             Script.goNextFormField,
-             Script.goPreviousFormField,
-             Script.goNextBlockquote,
-             Script.goPreviousBlockquote,
-             Script.goNextTable,
-             Script.goPreviousTable,
-             Script.goNextLiveRegion,
-             Script.goPreviousLiveRegion,
-             Script.goLastLiveRegion,
-             Script.advanceLivePoliteness,
-             Script.setLivePolitenessOff,
-             Script.monitorLiveRegions,
-             Script.reviewLiveAnnouncement,
-             Script.goCellLeft,
-             Script.goCellRight,
-             Script.goCellUp,
-             Script.goCellDown,
-             Script.goCellFirst,
-             Script.goCellLast]
-
         if controlCaretNavigation:
             debug.println(debug.LEVEL_CONFIGURATION,
                           "Orca is controlling the caret.")
@@ -189,14 +138,6 @@
         #
         self._documentFrameCaretContext = {}
 
-        # When navigating in a non-uniform table, one can move to a
-        # cell which spans multiple rows and/or columns.  When moving
-        # beyond that cell, into a cell that does NOT span multiple
-        # rows/columns, we want to be sure we land in the right place.
-        # Therefore, we'll store the coordinates from "our perspective."
-        #
-        self.lastTableCell = [-1, -1]
-
         # During a find we get caret-moved events reflecting the changing
         # screen contents.  The user can opt to have these changes announced.
         # If the announcement is enabled, it still only will be made if the
@@ -261,12 +202,47 @@
         """
         return SpeechGenerator(self)
 
+    def getEnabledStructNavTypes(self):
+        """Returns a list of the structural navigation object types
+        enabled in this script.
+        """
+
+        enabledTypes = [GeckoStructuralNavigation.ANCHOR,
+                        GeckoStructuralNavigation.BLOCKQUOTE,
+                        GeckoStructuralNavigation.BUTTON,
+                        GeckoStructuralNavigation.CHECK_BOX,
+                        GeckoStructuralNavigation.CHUNK,
+                        GeckoStructuralNavigation.COMBO_BOX,
+                        GeckoStructuralNavigation.ENTRY,
+                        GeckoStructuralNavigation.FORM_FIELD,
+                        GeckoStructuralNavigation.HEADING,
+                        GeckoStructuralNavigation.LANDMARK,
+                        GeckoStructuralNavigation.LIST,
+                        GeckoStructuralNavigation.LIST_ITEM,
+                        GeckoStructuralNavigation.LIVE_REGION,
+                        GeckoStructuralNavigation.RADIO_BUTTON,
+                        GeckoStructuralNavigation.TABLE,
+                        GeckoStructuralNavigation.TABLE_CELL,
+                        GeckoStructuralNavigation.UNVISITED_LINK,
+                        GeckoStructuralNavigation.VISITED_LINK]
+
+        return enabledTypes
+
+    def getStructuralNavigation(self):
+        """Returns the 'structural navigation' class for this script.
+        """
+        types = self.getEnabledStructNavTypes()
+        enable = structuralNavigationEnabled
+        return GeckoStructuralNavigation(self, types, enable)
+
     def setupInputEventHandlers(self):
         """Defines InputEventHandler fields for this script that can be
         called by the key and braille bindings.
         """
 
         default.Script.setupInputEventHandlers(self)
+        structNavHandlers = self.structuralNavigation.inputEventHandlers
+        self.inputEventHandlers.update(structNavHandlers)
 
         # Debug only.
         #
@@ -347,324 +323,6 @@
                 #
                 _("Causes the current combo box to be expanded."))
 
-        self.inputEventHandlers["goCellLeftHandler"] = \
-            input_event.InputEventHandler(
-                Script.goCellLeft,
-                # Translators: this is for navigating inside HTML tables.
-                #
-                _("Goes left one cell."))
-
-        self.inputEventHandlers["goCellRightHandler"] = \
-            input_event.InputEventHandler(
-                Script.goCellRight,
-                # Translators: this is for navigating inside HTML tables.
-                #
-                _("Goes right one cell."))
-
-        self.inputEventHandlers["goCellDownHandler"] = \
-            input_event.InputEventHandler(
-                Script.goCellDown,
-                # Translators: this is for navigating inside HTML tables.
-                #
-                _("Goes down one cell."))
-
-        self.inputEventHandlers["goCellUpHandler"] = \
-            input_event.InputEventHandler(
-                Script.goCellUp,
-                # Translators: this is for navigating inside HTML tables.
-                #
-                _("Goes up one cell."))
-
-        self.inputEventHandlers["goCellFirstHandler"] = \
-            input_event.InputEventHandler(
-                Script.goCellFirst,
-                # Translators: this is for navigating inside HTML tables.
-                #
-                _("Goes to the first cell in a table."))
-
-        self.inputEventHandlers["goCellLastHandler"] = \
-            input_event.InputEventHandler(
-                Script.goCellLast,
-                # Translators: this is for navigating inside HTML tables.
-                #
-                _("Goes to the last cell in a table."))
-
-        self.inputEventHandlers["goPreviousHeadingHandler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousHeading,
-                # Translators: this is for navigating HTML by headers
-                # (e.g., <h1>).
-                #
-                _("Goes to previous heading."))
-
-        self.inputEventHandlers["goNextHeadingHandler"] = \
-            input_event.InputEventHandler(
-                Script.goNextHeading,
-                # Translators: this is for navigating HTML by headers
-                # (e.g., <h1>).
-                #
-                _("Goes to next heading."))
-
-        self.inputEventHandlers["goPreviousHeading1Handler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousHeading1,
-                # Translators: this is for navigating HTML by headers
-                # (e.g., <h1>).
-                #
-                _("Goes to previous heading at level 1."))
-
-        self.inputEventHandlers["goNextHeading1Handler"] = \
-            input_event.InputEventHandler(
-                Script.goNextHeading1,
-                # Translators: this is for navigating HTML by headers
-                # (e.g., <h1>).
-                #
-                _("Goes to next heading at level 1."))
-
-        self.inputEventHandlers["goPreviousHeading2Handler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousHeading2,
-                # Translators: this is for navigating HTML by headers
-                # (e.g., <h2>).
-                #
-                _("Goes to previous heading at level 2."))
-
-        self.inputEventHandlers["goNextHeading2Handler"] = \
-            input_event.InputEventHandler(
-                Script.goNextHeading2,
-                # Translators: this is for navigating HTML by headers
-                # (e.g., <h2>).
-                #
-                _("Goes to next heading at level 2."))
-
-        self.inputEventHandlers["goPreviousHeading3Handler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousHeading3,
-                # Translators: this is for navigating HTML by headers
-                # (e.g., <h3>).
-                #
-                _("Goes to previous heading at level 3."))
-
-        self.inputEventHandlers["goNextHeading3Handler"] = \
-            input_event.InputEventHandler(
-                Script.goNextHeading3,
-                # Translators: this is for navigating HTML by headers
-                # (e.g., <h3>).
-                #
-                _("Goes to next heading at level 3."))
-
-        self.inputEventHandlers["goPreviousHeading4Handler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousHeading4,
-                # Translators: this is for navigating HTML by headers
-                # (e.g., <h4>).
-                #
-                _("Goes to previous heading at level 4."))
-
-        self.inputEventHandlers["goNextHeading4Handler"] = \
-            input_event.InputEventHandler(
-                Script.goNextHeading4,
-                # Translators: this is for navigating HTML by headers
-                # (e.g., <h4>).
-                #
-                _("Goes to next heading at level 4."))
-
-        self.inputEventHandlers["goPreviousHeading5Handler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousHeading5,
-                # Translators: this is for navigating HTML by headers
-                # (e.g., <h5>).
-                #
-                _("Goes to previous heading at level 5."))
-
-        self.inputEventHandlers["goNextHeading5Handler"] = \
-            input_event.InputEventHandler(
-                Script.goNextHeading5,
-                # Translators: this is for navigating HTML by headers
-                # (e.g., <h5>).
-                #
-                _("Goes to next heading at level 5."))
-
-        self.inputEventHandlers["goPreviousHeading6Handler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousHeading6,
-                # Translators: this is for navigating HTML by headers
-                # (e.g., <h6>).
-                #
-                _("Goes to previous heading at level 6."))
-
-        self.inputEventHandlers["goNextHeading6Handler"] = \
-            input_event.InputEventHandler(
-                Script.goNextHeading6,
-                # Translators: this is for navigating HTML by headers
-                # (e.g., <h6>).
-                #
-                _("Goes to next heading at level 6."))
-
-        self.inputEventHandlers["goPreviousChunkHandler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousChunk,
-                # Translators: this is for navigating HTML in a structural
-                # manner, where a 'large object' is a logical chunk of
-                # text, such as a paragraph, a list, a table, etc.
-                #
-                _("Goes to previous large object."))
-
-        self.inputEventHandlers["goNextChunkHandler"] = \
-            input_event.InputEventHandler(
-                Script.goNextChunk,
-                # Translators: this is for navigating HTML in a structural
-                # manner, where a 'large object' is a logical chunk of
-                # text, such as a paragraph, a list, a table, etc.
-                #
-                _("Goes to next large object."))
-
-        self.inputEventHandlers["goPreviousLandmark"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousLandmark,
-                # Translators: this is for navigating to the previous ARIA
-                # role landmark.  ARIA role landmarks are the W3C defined HTML
-                # tag attribute 'role' used to identify important part of
-                # webpage like banners, main context, search etc.
-                #
-                _("Goes to previous landmark."))
-
-        self.inputEventHandlers["goNextLandmark"] = \
-            input_event.InputEventHandler(
-                Script.goNextLandmark,
-                # Translators: this is for navigating to the next ARIA
-                # role landmark.
-                #
-                _("Goes to next landmark."))
-
-        self.inputEventHandlers["goPreviousListHandler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousList,
-                # Translators: this is for navigating between bulleted/numbered
-                # lists in HTML
-                #
-                _("Goes to previous list."))
-
-        self.inputEventHandlers["goNextListHandler"] = \
-            input_event.InputEventHandler(
-                Script.goNextList,
-                # Translators: this is for navigating between bulleted/numbered
-                # lists in HTML
-                #
-                _("Goes to next list."))
-
-        self.inputEventHandlers["goPreviousListItemHandler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousListItem,
-                # Translators: this is for navigating between bulleted/numbered
-                # list items in HTML
-                #
-                _("Goes to previous list item."))
-
-        self.inputEventHandlers["goNextListItemHandler"] = \
-            input_event.InputEventHandler(
-                Script.goNextListItem,
-                # Translators: this is for navigating between bulleted/numbered
-                # list items in HTML
-                #
-                _("Goes to next list item."))
-
-        self.inputEventHandlers["goPreviousUnvisitedLinkHandler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousUnvisitedLink,
-                # Translators: this is for navigating between links in HTML
-                #
-                _("Goes to previous unvisited link."))
-
-        self.inputEventHandlers["goNextUnvisitedLinkHandler"] = \
-            input_event.InputEventHandler(
-                Script.goNextUnvisitedLink,
-                # Translators: this is for navigating between links in HTML
-                #
-               _("Goes to next unvisited link."))
-
-        self.inputEventHandlers["goPreviousVisitedLinkHandler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousVisitedLink,
-                # Translators: this is for navigating between links in HTML
-                #
-                _("Goes to previous visited link."))
-
-        self.inputEventHandlers["goNextVisitedLinkHandler"] = \
-            input_event.InputEventHandler(
-                Script.goNextVisitedLink,
-                # Translators: this is for navigating between links in HTML
-                #
-                _("Goes to next visited link."))
-
-        self.inputEventHandlers["goPreviousFormFieldHandler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousFormField,
-                # Translators: this is for navigating between form fields in
-                # HTML
-                #
-                _("Goes to previous form field."))
-
-        self.inputEventHandlers["goNextFormFieldHandler"] = \
-            input_event.InputEventHandler(
-                Script.goNextFormField,
-                # Translators: this is for navigating between form fields in
-                # HTML
-                #
-                _("Goes to next form field."))
-
-        self.inputEventHandlers["goPreviousBlockquoteHandler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousBlockquote,
-                # Translators: this is for navigating among blockquotes in
-                # HTML
-                #
-                _("Goes to previous blockquote."))
-
-        self.inputEventHandlers["goNextBlockquoteHandler"] = \
-            input_event.InputEventHandler(
-                Script.goNextBlockquote,
-                # Translators: this is for navigating among blockquotes in
-                # HTML
-                #
-                _("Goes to next blockquote."))
-
-        self.inputEventHandlers["goPreviousTableHandler"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousTable,
-                # Translators: this is for navigating between tables in HTML
-                #
-                _("Goes to previous table."))
-
-        self.inputEventHandlers["goNextTableHandler"] = \
-            input_event.InputEventHandler(
-                Script.goNextTable,
-                # Translators: this is for navigating between tables in HTML
-                #
-                _("Goes to next table."))
-
-        self.inputEventHandlers["goPreviousLiveRegion"] = \
-            input_event.InputEventHandler(
-                Script.goPreviousLiveRegion,
-                # Translators: this is for navigating between live regions
-                #
-                _("Goes to previous live region."))
-
-        self.inputEventHandlers["goNextLiveRegion"] = \
-            input_event.InputEventHandler(
-                Script.goNextLiveRegion,
-                # Translators: this is for navigating between live regions
-                #
-                _("Goes to next live region."))
-
-        self.inputEventHandlers["goLastLiveRegion"] = \
-            input_event.InputEventHandler(
-                Script.goLastLiveRegion,
-                # Translators: this is for navigating to the last live region
-                # to make an announcement.
-                #
-                _("Goes to last live region."))
-
         self.inputEventHandlers["advanceLivePoliteness"] = \
             input_event.InputEventHandler(
                 Script.advanceLivePoliteness,
@@ -725,18 +383,6 @@
                 #
                 _("Switches between Gecko native and Orca caret navigation."))
 
-        self.inputEventHandlers["toggleStructuralNavigationHandler"] = \
-            input_event.InputEventHandler(
-                Script.toggleStructuralNavigation,
-                # Translators: the structural navigation keys are designed
-                # to move the caret around the HTML content by object type.
-                # Thus H moves you to the next heading, Shift H to the
-                # previous heading, T to the next table, and so on. Some
-                # users prefer to turn this off to use Firefox's search
-                # when typing feature.
-                #
-                _("Toggles structural navigation keys."))
-
         self.inputEventHandlers["sayAllHandler"] = \
             input_event.InputEventHandler(
                 Script.sayAll,
@@ -852,50 +498,8 @@
 
         keyBindings.add(
             keybindings.KeyBinding(
-                "Right",
-                fullModMask,
-                shiftAltModMask,
-                self.inputEventHandlers["goCellRightHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "Left",
-                fullModMask,
-                shiftAltModMask,
-                self.inputEventHandlers["goCellLeftHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "Up",
-                fullModMask,
-                shiftAltModMask,
-                self.inputEventHandlers["goCellUpHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "Down",
-                fullModMask,
-                shiftAltModMask,
-                self.inputEventHandlers["goCellDownHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
                 "Home",
                 fullModMask,
-                shiftAltModMask,
-                self.inputEventHandlers["goCellFirstHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "End",
-                fullModMask,
-                shiftAltModMask,
-                self.inputEventHandlers["goCellLastHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "Home",
-                fullModMask,
                 controlModMask,
                 self.inputEventHandlers["goTopOfFileHandler"]))
 
@@ -925,246 +529,6 @@
 
         keyBindings = default.Script.getKeyBindings(self)
 
-        # NOTE: We include ALT and CONTROL in all the bindings below so as
-        # to not conflict with menu and other mnemonics.
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "h",
-                fullModMask,
-                shiftModMask,
-                self.inputEventHandlers["goPreviousHeadingHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "h",
-                fullModMask,
-                0,
-                self.inputEventHandlers["goNextHeadingHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "1",
-                fullModMask,
-                shiftModMask,
-                self.inputEventHandlers["goPreviousHeading1Handler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "1",
-                fullModMask,
-                0,
-                self.inputEventHandlers["goNextHeading1Handler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "2",
-                fullModMask,
-                shiftModMask,
-                self.inputEventHandlers["goPreviousHeading2Handler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "2",
-                fullModMask,
-                0,
-                self.inputEventHandlers["goNextHeading2Handler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "3",
-                fullModMask,
-                shiftModMask,
-                self.inputEventHandlers["goPreviousHeading3Handler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "3",
-                fullModMask,
-                0,
-                self.inputEventHandlers["goNextHeading3Handler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "4",
-                fullModMask,
-                shiftModMask,
-                self.inputEventHandlers["goPreviousHeading4Handler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "4",
-                fullModMask,
-                0,
-                self.inputEventHandlers["goNextHeading4Handler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "5",
-                fullModMask,
-                0,
-                self.inputEventHandlers["goNextHeading5Handler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "5",
-                fullModMask,
-                shiftModMask,
-                self.inputEventHandlers["goPreviousHeading5Handler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "6",
-                fullModMask,
-                shiftModMask,
-                self.inputEventHandlers["goPreviousHeading6Handler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "6",
-                fullModMask,
-                0,
-                self.inputEventHandlers["goNextHeading6Handler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "o",
-                fullModMask,
-                shiftModMask,
-                self.inputEventHandlers["goPreviousChunkHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "o",
-                fullModMask,
-                0,
-                self.inputEventHandlers["goNextChunkHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "l",
-                fullModMask,
-                shiftModMask,
-                self.inputEventHandlers["goPreviousListHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "l",
-                fullModMask,
-                0,
-                self.inputEventHandlers["goNextListHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "i",
-                fullModMask,
-                shiftModMask,
-                self.inputEventHandlers["goPreviousListItemHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "i",
-                fullModMask,
-                0,
-                self.inputEventHandlers["goNextListItemHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "u",
-                fullModMask,
-                shiftModMask,
-                self.inputEventHandlers["goPreviousUnvisitedLinkHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "u",
-                fullModMask,
-                0,
-                self.inputEventHandlers["goNextUnvisitedLinkHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "v",
-                fullModMask,
-                shiftModMask,
-                self.inputEventHandlers["goPreviousVisitedLinkHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "v",
-                fullModMask,
-                0,
-                self.inputEventHandlers["goNextVisitedLinkHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "Tab",
-                fullModMask,
-                orcaShiftModMask,
-                self.inputEventHandlers["goPreviousFormFieldHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "Tab",
-                fullModMask,
-                orcaModMask,
-                self.inputEventHandlers["goNextFormFieldHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "q",
-                fullModMask,
-                shiftModMask,
-                self.inputEventHandlers["goPreviousBlockquoteHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "q",
-                fullModMask,
-                0,
-                self.inputEventHandlers["goNextBlockquoteHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "t",
-                fullModMask,
-                shiftModMask,
-                self.inputEventHandlers["goPreviousTableHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "t",
-                fullModMask,
-                0,
-                self.inputEventHandlers["goNextTableHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "r",
-                (1 << pyatspi.MODIFIER_SHIFT \
-                 | 1 << pyatspi.MODIFIER_ALT \
-                 | 1 << pyatspi.MODIFIER_CONTROL),
-                1 << pyatspi.MODIFIER_SHIFT,
-                self.inputEventHandlers["goPreviousLiveRegion"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "r",
-                (1 << pyatspi.MODIFIER_SHIFT \
-                 | 1 << pyatspi.MODIFIER_ALT \
-                 | 1 << pyatspi.MODIFIER_CONTROL),
-                0,
-                self.inputEventHandlers["goNextLiveRegion"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
-                "y",
-                (1 << pyatspi.MODIFIER_SHIFT \
-                 | 1 << pyatspi.MODIFIER_ALT \
-                 | 1 << pyatspi.MODIFIER_CONTROL),
-                0,
-                self.inputEventHandlers["goLastLiveRegion"]))
-
         # keybindings to provide chat room message history.
         messageKeys = [ "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9" ]
         for messageKey in messageKeys:
@@ -1216,13 +580,6 @@
 
         keyBindings.add(
             keybindings.KeyBinding(
-                "z",
-                fullModMask,
-                orcaModMask,
-                self.inputEventHandlers["toggleStructuralNavigationHandler"]))
-
-        keyBindings.add(
-            keybindings.KeyBinding(
                 "Right",
                 fullModMask,
                 orcaModMask,
@@ -1239,25 +596,10 @@
             for keyBinding in self.__getArrowBindings().keyBindings:
                 keyBindings.add(keyBinding)
 
-        #####################################################################
-        #                                                                   #
-        #  Unbound handlers                                                 #
-        #                                                                   #
-        #####################################################################
-        keyBindings.add(
-            keybindings.KeyBinding(
-                None,
-                0,
-                0,
-                self.inputEventHandlers["goPreviousLandmark"]))
+        bindings = self.structuralNavigation.keyBindings
+        for keyBinding in bindings.keyBindings:
+            keyBindings.add(keyBinding)
 
-        keyBindings.add(
-             keybindings.KeyBinding(
-                None,
-                0,
-                0,
-                self.inputEventHandlers["goNextLandmark"]))
-
         return keyBindings
 
     def getAppPreferencesGUI(self):
@@ -1310,7 +652,7 @@
         gtk.Box.pack_start(generalVBox, self.structuralNavigationCheckButton,
                            False, False, 0)
         gtk.ToggleButton.set_active(self.structuralNavigationCheckButton,
-                                    structuralNavigationEnabled)
+                                    self.structuralNavigation.enabled)
 
         # Translators: when the user arrows up and down in HTML content,
         # it is some times beneficial to always position the cursor at the
@@ -1567,8 +909,7 @@
         object will be use by setAppState to restore any state information
         that was being maintained by the script."""
         return [default.Script.getAppState(self),
-                self._documentFrameCaretContext,
-                self.lastTableCell]
+                self._documentFrameCaretContext]
 
     def setAppState(self, appState):
         """Sets the application state using the given appState object.
@@ -1578,8 +919,7 @@
         """
         try:
             [defaultAppState,
-             self._documentFrameCaretContext,
-             self.lastTableCell] = appState
+             self._documentFrameCaretContext] = appState
             default.Script.setAppState(self, defaultAppState)
         except:
             debug.printException(debug.LEVEL_WARNING)
@@ -1615,7 +955,7 @@
             if handler and handler.function in self._caretNavigationFunctions:
                 return self.useCaretNavigationModel(keyboardEvent)
             elif handler \
-                and handler.function in self._structuralNavigationFunctions:
+                 and handler.function in self.structuralNavigation.functions:
                 return self.useStructuralNavigationModel()
             else:
                 consumes = handler != None
@@ -1624,7 +964,7 @@
             if handler and handler.function in self._caretNavigationFunctions:
                 return self.useCaretNavigationModel(keyboardEvent)
             elif handler \
-                and handler.function in self._structuralNavigationFunctions:
+                 and handler.function in self.structuralNavigation.functions:
                 return self.useStructuralNavigationModel()
             else:
                 consumes = handler != None
@@ -3435,7 +2775,7 @@
                                      pyatspi.ROLE_LIST_ITEM,
                                      pyatspi.ROLE_MENU_ITEM]
 
-        if not structuralNavigationEnabled:
+        if not self.structuralNavigation.enabled:
             return False
 
         if not self.isNavigableAria(orca_state.locusOfFocus):
@@ -3814,32 +3154,6 @@
 
         return [0, 0]
 
-    def isSameCell(self, obj, coordinates1, coordinates2):
-        """Returns True if coordinates1 and coordinates2 refer to the
-        same cell in the specified table.
-
-        Arguments:
-        - obj: the table in which to compare the coordinates
-        - coordinates1: [row, col]
-        - coordinates2: [row, col]
-        """
-
-        if coordinates1 == coordinates2:
-            return True
-
-        try:
-            table = obj.queryTable()
-        except:
-            pass
-        else:
-            cell1 = table.getAccessibleAt(coordinates1[0], 
-                                          coordinates1[1])
-            cell2 = table.getAccessibleAt(coordinates2[0],
-                                          coordinates2[1])
-            return self.isSameObject(cell1, cell2)
-
-        return False
-
     def isBlankCell(self, obj):
         """Returns True if the table cell is empty or consists of a single
         non-breaking space.
@@ -3858,231 +3172,6 @@
 
             return True
 
-    def isNonUniformTable(self, obj):
-        """Returns True if the obj is a non-uniform table (i.e. a table
-        where at least one cell spans multiple rows and/or columns).
-
-        Arguments:
-        - obj: the table to examine
-        """
-
-        try:
-            table = obj.queryTable()
-        except:
-            pass
-        else:
-            for i in xrange(obj.childCount):
-                [isCell, row, col, rowExtents, colExtents, isSelected] = \
-                                       table.getRowColumnExtentsAtIndex(i)
-                if (rowExtents > 1) or (colExtents > 1):
-                    return True
-
-        return False
-
-    def isHeader(self, obj):
-        """Returns True if the table cell is a header"""
-
-        if not obj:
-            return False
-
-        attributes = obj.getAttributes()
-        if attributes:
-            for attribute in attributes:
-                if attribute == "tag:TH":
-                    return True
-
-        return False
-
-    def isInHeaderRow(self, obj):
-        """Returns True if all of the cells in the same row as this cell are
-        headers.
-
-        Arguments:
-        - obj: the table cell whose row is to be examined
-        """
-
-        if obj and obj.getRole() == pyatspi.ROLE_TABLE_CELL:
-            parent = self.getAncestor(obj,
-                                      [pyatspi.ROLE_TABLE],
-                                      [pyatspi.ROLE_DOCUMENT_FRAME])
-            try:
-                table = parent.queryTable()
-            except:
-                return False
-            else:
-                index = self.getCellIndex(obj)
-                row = table.getRowAtIndex(index)
-                for col in xrange(table.nColumns):
-                    cell = table.getAccessibleAt(row, col)
-                    if not self.isHeader(cell):
-                        return False
-
-        return True
-
-    def isInHeaderColumn(self, obj):
-        """Returns True if all of the cells in the same column as this cell
-        are headers.
-
-        Arguments:
-        - obj: the table cell whose column is to be examined
-        """
-
-        if obj and obj.getRole() == pyatspi.ROLE_TABLE_CELL:
-            parent = self.getAncestor(obj,
-                                      [pyatspi.ROLE_TABLE],
-                                      [pyatspi.ROLE_DOCUMENT_FRAME])
-            try:
-                table = parent.queryTable()
-            except:
-                return False
-            else:
-                index = self.getCellIndex(obj)
-                col = table.getColumnAtIndex(index)
-                for row in xrange(table.nRows):
-                    cell = table.getAccessibleAt(row, col)
-                    if not self.isHeader(cell):
-                        return False
-
-        return True
-
-    def getRowHeaders(self, obj):
-        """Returns a list of table cells that serve as a row header for
-        the specified TABLE_CELL.
-        """
-
-        rowHeaders = []
-        if not obj:
-            return rowHeaders
-
-        try:
-            table = obj.parent.queryTable()
-        except:
-            pass
-        else:
-            [row, col] = self.getCellCoordinates(obj)
-            # Theoretically, we should be able to quickly get the text
-            # of a {row, column}Header via get{Row,Column}Description().
-            # Mozilla doesn't expose the information that way, however.
-            # get{Row, Column}Header seems to work sometimes.
-            #
-            header = table.getRowHeader(row)
-            if header:
-                rowHeaders.append(header)
-
-            # Headers that are strictly marked up with <th> do not seem
-            # to be exposed through get{Row, Column}Header.
-            #
-            else:
-                # If our cell spans multiple rows, we want to get all of
-                # the headers that apply.
-                #
-                rowspan = table.getRowExtentAt(row, col)
-                for r in range(row, row+rowspan):
-                    # We could have multiple headers for a given row, one
-                    # header per column.  Presumably all of the headers are
-                    # prior to our present location.
-                    #
-                    for c in range(0, col):
-                        cell = table.getAccessibleAt(r, c)
-                        text = self.queryNonEmptyText(cell)
-                        if self.isHeader(cell) and text \
-                           and not cell in rowHeaders:
-                            rowHeaders.append(cell)
-
-        return rowHeaders
-
-    def getColumnHeaders(self, obj):
-        """Returns a list of table cells that serve as a column header for
-        the specified TABLE_CELL.
-        """
-
-        columnHeaders = []
-        if not obj:
-            return columnHeaders
-
-        try:
-            table = obj.parent.queryTable()
-        except:
-            pass
-        else:
-            [row, col] = self.getCellCoordinates(obj)
-            # Theoretically, we should be able to quickly get the text
-            # of a {row, column}Header via get{Row,Column}Description().
-            # Mozilla doesn't expose the information that way, however.
-            # get{Row, Column}Header seems to work sometimes.
-            #
-            header = table.getColumnHeader(col)
-            if header:
-                columnHeaders.append(header)
-
-            # Headers that are strictly marked up with <th> do not seem
-            # to be exposed through get{Row, Column}Header.
-            #
-            else:
-                # If our cell spans multiple columns, we want to get all of
-                # the headers that apply.
-                #
-                colspan = table.getColumnExtentAt(row, col)
-                for c in range(col, col+colspan):
-                    # We could have multiple headers for a given column, one
-                    # header per row.  Presumably all of the headers are
-                    # prior to our present location.
-                    #
-                    for r in range(0, row):
-                        cell = table.getAccessibleAt(r, c)
-                        text = self.queryNonEmptyText(cell)
-                        if self.isHeader(cell) and text \
-                           and not cell in columnHeaders:
-                            columnHeaders.append(cell)
-
-        return columnHeaders
-
-    def getCellSpanInfo(self, obj):
-        """Returns a string reflecting the number of rows and/or columns
-        spanned by a table cell when multiple rows and/or columns are spanned.
-        """
-
-        if not obj or (obj.getRole() != pyatspi.ROLE_TABLE_CELL):
-            return
-
-        [row, col] = self.getCellCoordinates(obj)
-        table = obj.parent.queryTable()
-        rowspan = table.getRowExtentAt(row, col)
-        colspan = table.getColumnExtentAt(row, col)
-        spanString = None
-        if (colspan > 1) and (rowspan > 1):
-            # Translators: The cell here refers to a cell within an HTML
-            # table.  We need to announce when the cell occupies or "spans"
-            # more than a single row and/or column.
-            #
-            spanString = _("Cell spans %d rows and %d columns") % \
-                          (rowspan, colspan)
-        elif (colspan > 1):
-            # Translators: The cell here refers to a cell within an HTML
-            # table.  We need to announce when the cell occupies or "spans"
-            # more than a single row and/or column.
-            #
-            spanString = _("Cell spans %d columns") % colspan
-        elif (rowspan > 1):
-            # Translators: The cell here refers to a cell within an HTML
-            # table.  We need to announce when the cell occupies or "spans"
-            # more than a single row and/or column.
-            #
-            spanString = _("Cell spans %d rows") % rowspan
-
-        return spanString
-
-    def getTableCaption(self, obj):
-        """Returns the ROLE_CAPTION object of a ROLE_TABLE object or None
-        if the caption cannot be found.
-        """
-
-        for child in obj:
-            if child and (child.getRole() == pyatspi.ROLE_CAPTION):
-                return child
-
-        return None
-
     def getLinkBasename(self, obj):
         """Returns the relevant information from the URI.  The idea is
         to attempt to strip off all prefix and suffix, much like the
@@ -4138,20 +3227,6 @@
 
         return isField
 
-    def isBlockquote(self, obj):
-        """Returns True if the object is a blockquote"""
-
-        if not obj:
-            return False
-
-        attributes = obj.getAttributes()
-        if attributes:
-            for attribute in attributes:
-                if attribute == "tag:BLOCKQUOTE":
-                    return True
-
-        return False
-
     def isLineBreakChar(self, obj, offset):
         """Returns True of the character at the given offset within
         obj is a line break character (i.e. <br />) or is a newline
@@ -4288,7 +3363,8 @@
         """Returns the object and last caret offset at the bottom of the
          document frame."""
 
-        obj = self.getLastObject()
+        documentFrame = self.getDocumentFrame()
+        obj = self.getLastObject(documentFrame)
         offset = 0
 
         # obj should now be the very last item in the entire document frame
@@ -4299,7 +3375,7 @@
         if text:
             offset = text.characterCount - 1
         else:
-            obj = self.findPreviousObject(obj)
+            obj = self.findPreviousObject(obj, documentFrame)
 
         while obj:
             [lastObj, lastOffset] = self.findNextCaretInOrder(obj, offset)
@@ -4311,13 +3387,12 @@
 
         return [obj, offset]
 
-    def getLastObject(self):
+    def getLastObject(self, documentFrame):
         """Returns the last object in the document frame"""
 
-        documentFrame = self.getDocumentFrame()
         lastChild = documentFrame[documentFrame.childCount - 1]
         while lastChild:
-            lastObj = self.findNextObject(lastChild)
+            lastObj = self.findNextObject(lastChild, documentFrame)
             if lastObj:
                 lastChild = lastObj
             else:
@@ -4992,12 +4067,14 @@
 
         return guess
 
-    def guessTheLabel(self, obj):
+    def guessTheLabel(self, obj, focusedOnly=True):
         """Attempts to guess what the label of an unlabeled form control
         might be.
 
         Arguments
         - obj: the form field about which to take a guess
+        - focusedOnly: If True, only take guesses about the form field
+          with focus.
 
         Returns the text which we think might be the label or None if we
         give up.
@@ -5015,8 +4092,9 @@
         # If we're not in the document frame, we don't want to be guessing.
         # We also don't want to be guessing if the item doesn't have focus.
         #
+        isFocused = obj.getState().contains(pyatspi.STATE_FOCUSED)
         if not self.inDocumentContent() \
-           or not obj.getState().contains(pyatspi.STATE_FOCUSED) \
+           or (focusedOnly and not isFocused) \
            or self.isAriaWidget(obj):
             return guess
 
@@ -5334,7 +4412,7 @@
 
         return [None, -1]
 
-    def findPreviousObject(self, obj):
+    def findPreviousObject(self, obj, documentFrame):
         """Finds the object prior to this one, where the tree we're
         dealing with is a DOM and 'prior' means the previous object
         in a linear presentation sense.
@@ -5345,7 +4423,6 @@
 
         previousObj = None
         characterOffset = 0
-        documentFrame = self.getDocumentFrame()
 
         # If the object is the document frame, the previous object is
         # the one that follows us relative to our offset.
@@ -5360,7 +4437,7 @@
             else:
                 # We're likely at the very end of the document
                 # frame.
-                previousObj = self.getLastObject()
+                previousObj = self.getLastObject(documentFrame)
         else:
             # [[[TODO: HACK - WDW defensive programming because Gecko
             # ally hierarchies are not always working.  Objects say
@@ -5430,7 +4507,7 @@
 
         return previousObj
 
-    def findNextObject(self, obj):
+    def findNextObject(self, obj, documentFrame):
         """Finds the object after to this one, where the tree we're
         dealing with is a DOM and 'next' means the next object
         in a linear presentation sense.
@@ -5441,7 +4518,6 @@
 
         nextObj = None
         characterOffset = 0
-        documentFrame = self.getDocumentFrame()
 
         # If the object is the document frame, the next object is
         # the one that follows us relative to our offset.
@@ -5535,329 +4611,6 @@
 
         return nextObj
 
-    def findPreviousRole(self, roles, wrap, currentObj=None):
-        if settings.useCollection:
-            try:
-                # The docframe is our collection
-                docframe = self.getDocumentFrame()
-                col = docframe.queryCollection()
-            except:
-                debug.printException(debug.LEVEL_SEVERE)
-                # Collection is probably not implemented, use the fallback
-                return self.iterFindPreviousRole(roles, wrap, currentObj)
-
-            try:
-                # We have our Collection so define our matchRule and go find it
-                stateset = pyatspi.StateSet()
-                rule = col.createMatchRule(stateset.raw(), col.MATCH_NONE,
-                            "", col.MATCH_NONE,
-                            roles, col.MATCH_ANY,
-                            "", col.MATCH_NONE,
-                            False)
-                retval = self.findPrevByMatchRule(col, rule, wrap, currentObj)
-                # we created the matchRule so we need to free it
-                col.freeMatchRule(rule)
-                return retval
-            except:
-                # we have created our matchRule at this point so free it
-                col.freeMatchRule(rule)
-                debug.printException(debug.LEVEL_SEVERE)
-                # Collection is probably not implemented, use the fallback
-                return self.iterFindPreviousRole(roles, wrap, currentObj)
-        else:
-            return self.iterFindPreviousRole(roles, wrap, currentObj)
-
-    def iterFindPreviousRole(self, roles, wrap, currentObj=None):
-        """Finds the caret offset at the beginning of the next object
-        using the given roles list as a pattern to match.
-
-        Arguments:
-        -roles: a list of roles from rolenames.py
-        -wrap: if True and the top of the document is reached, move
-               to the bottom and keep looking.
-        -currentObj: the object from which the search should begin
-
-        Returns: [obj, wrapped] where wrapped is a boolean reflecting
-        whether wrapping took place.
-        """
-
-        if not currentObj:
-            [currentObj, characterOffset] = self.getCaretContext()
-
-        ancestors = []
-        nestableRoles = [pyatspi.ROLE_LIST, pyatspi.ROLE_TABLE]
-        obj = currentObj.parent
-        while obj:
-            ancestors.append(obj)
-            obj = obj.parent
-
-        wrapped = False
-        obj = self.findPreviousObject(currentObj)
-        while obj:
-            isNestedItem = ((obj != currentObj.parent) \
-                            and (currentObj.parent.getRole() == obj.getRole()) \
-                            and (obj.getRole() in nestableRoles))
-            if ((not obj in ancestors) or isNestedItem) \
-               and (obj.getRole() in roles) \
-               and (not self.isLayoutOnly(obj)):
-                if wrapped and self.isSameObject(currentObj, obj):
-                    obj = None
-                return [obj, wrapped]
-            else:
-                obj = self.findPreviousObject(obj)
-                if not obj and wrap and not wrapped:
-                    obj = self.getLastObject()
-                    wrapped = True
-
-        return [None, wrapped]
-
-    def findNextRole(self, roles, wrap, currentObj=None):
-        if settings.useCollection:
-            try:
-                # The docframe is our collection
-                docframe = self.getDocumentFrame()
-                col = docframe.queryCollection()
-            except:
-                debug.printException(debug.LEVEL_SEVERE)
-                # Collection is probably not implemented, use the fallback
-                return self.iterFindNextRole(roles, wrap, currentObj)
-
-            try:
-                # We have our Collection so define our matchRule and go find it
-                stateset = pyatspi.StateSet()
-                rule = col.createMatchRule(stateset.raw(), col.MATCH_NONE,
-                            "", col.MATCH_NONE,
-                            roles, col.MATCH_ANY,
-                            "", col.MATCH_NONE,
-                            False)
-                retval = self.findNextByMatchRule(col, rule, wrap, currentObj)
-                # we created the matchRule so we need to free it
-                col.freeMatchRule(rule)
-                return retval
-            except:
-                # we created the matchRule at this point so free it
-                col.freeMatchRule(rule)
-                debug.printException(debug.LEVEL_SEVERE)
-                # Collection is probably not implemented, use the fallback
-                return self.iterFindNextRole(roles, wrap, currentObj)
-        else:
-            return self.iterFindNextRole(roles, wrap, currentObj)
-
-    def iterFindNextRole(self, roles, wrap, currentObj=None):
-        """Finds the caret offset at the beginning of the next object
-        using the given roles list as a pattern to match or not match.
-
-        Arguments:
-        -roles: a list of roles from rolenames.py
-        -wrap: if True and the bottom of the document is reached, move
-               to the top and keep looking.
-        -currentObj: the object from which the search should begin
-
-        Returns: [obj, wrapped] where wrapped is a boolean reflecting
-        whether wrapping took place.
-        """
-
-        if not currentObj:
-            [currentObj, characterOffset] = self.getCaretContext()
-
-        ancestors = []
-        obj = currentObj.parent
-        while obj:
-            ancestors.append(obj)
-            obj = obj.parent
-
-        wrapped = False
-        obj = self.findNextObject(currentObj)
-        if not obj and wrap:
-            documentFrame = self.getDocumentFrame()
-            obj = documentFrame[0]
-            wrapped = True
-        while obj:
-            if (not obj in ancestors) and (obj.getRole() in roles) \
-                and (not self.isLayoutOnly(obj)):
-                if wrapped and self.isSameObject(currentObj, obj):
-                    obj = None
-                return [obj, wrapped]
-            else:
-                obj = self.findNextObject(obj)
-                if not obj and wrap and not wrapped:
-                    documentFrame = self.getDocumentFrame()
-                    obj = documentFrame[0]
-                    wrapped = True
-
-        return [None, wrapped]
-
-    def findPrevByMatchRule(self, col, matchrule,
-                                  wrap, currentObj, allowNesting=False):
-        # get our current object
-        if not currentObj:
-            [currentObj, characterOffset] = self.getCaretContext()
-
-        # Get the ancestors.  We won't stop on any of them.
-        ancestors = []
-        obj = currentObj.parent
-        if allowNesting:
-            ancestors.append(obj)
-        else:
-            while obj:
-                ancestors.append(obj)
-                obj = obj.parent
-
-        wrapped = False
-        rs = col.getMatchesTo(currentObj, matchrule,
-                              col.SORT_ORDER_CANONICAL,
-                              col.TREE_INORDER, True, 1, True)
-        while True:
-            if len(rs) == 0:
-                if wrapped:
-                    return [None, True]
-                elif wrap:
-                    lastobj = self.getLastObject()
-                    # Collection does not do an inclusive search, meaning
-                    # that the start object is not part of the search.  So
-                    # we need to test the lastobj separately using the given
-                    # matchRule.  We don't have this problem for 'Next' because
-                    # the startobj is the doc frame.
-                    #
-                    secondlastobj = self.findPreviousObject(lastobj)
-                    rs = col.getMatchesFrom(secondlastobj, matchrule,
-                              col.SORT_ORDER_CANONICAL,
-                              col.TREE_INORDER, 1, True)
-                    if len(rs) > 0:
-                        return [rs[0], True]
-                    else:
-                        rs = col.getMatchesTo(lastobj, matchrule,
-                              col.SORT_ORDER_CANONICAL,
-                              col.TREE_INORDER, True, 1, True)
-                        wrapped = True
-                 # caller doesn't want us to wrap and we haven't found anything
-                else:
-                    return [None, False]
-            elif len(rs) > 0:
-                if rs[0] in ancestors:
-                    rs = col.getMatchesTo(rs[0], matchrule,
-                              col.SORT_ORDER_CANONICAL,
-                              col.TREE_INORDER, True, 1, True)
-                else:
-                    return [rs[0], wrapped]
-
-    def findNextByMatchRule(self, col, matchrule, wrap, currentObj=None):
-        # get our current object
-        if not currentObj:
-            [currentObj, characterOffset] = self.getCaretContext()
-
-        # go find the next match
-        rs = col.getMatchesFrom(currentObj,
-                                    matchrule,
-                                    col.SORT_ORDER_CANONICAL,
-                                    col.TREE_INORDER,
-                                    1,
-                                    True)
-
-        if len(rs) > 0:
-            return [rs[0], False]
-        elif wrap:
-            # We didn't find anything so start at the doc frame and try again
-            rs = col.getMatchesFrom(self.getDocumentFrame(),
-                                    matchrule,
-                                    col.SORT_ORDER_CANONICAL,
-                                    col.TREE_INORDER,
-                                    1,
-                                    True)
-            if len(rs) > 0:
-                return [rs[0], True]
-            else:
-                return [None, True]
-        else:
-            return [None, False]
-
-    def findPrevByPredicate(self, pred, wrap, currentObj=None):
-        """Finds the caret offset at the beginning of the previous object
-        using the given predicate as a pattern to match.
-
-        Arguments:
-        -pred: a python callable that takes an accessible argument and
-               returns true/false based on some match criteria
-        -wrap: if True and the top of the document is reached, move
-               to the bottom and keep looking.
-        -currentObj: the object from which the search should begin
-
-        Returns: [obj, wrapped] where wrapped is a boolean reflecting
-        whether wrapping took place.
-        """
-
-        if not currentObj:
-            [currentObj, characterOffset] = self.getCaretContext()
-
-        ancestors = []
-        nestableRoles = [pyatspi.ROLE_LIST, pyatspi.ROLE_TABLE]
-        obj = currentObj.parent
-        while obj:
-            ancestors.append(obj)
-            obj = obj.parent
-
-        wrapped = False
-        obj = self.findPreviousObject(currentObj)
-        while obj:
-            isNestedItem = ((obj != currentObj.parent) \
-                            and (currentObj.parent.getRole() == obj.getRole()) \
-                            and (obj.getRole() in nestableRoles))
-            if ((not obj in ancestors) or isNestedItem) and pred(obj):
-                if wrapped and self.isSameObject(currentObj, obj):
-                    obj = None
-                return [obj, wrapped]
-            else:
-                obj = self.findPreviousObject(obj)
-                if not obj and wrap and not wrapped:
-                    obj = self.getLastObject()
-                    wrapped = True
-
-        return [None, wrapped]
-
-    def findNextByPredicate(self, pred, wrap, currentObj=None):
-        """Finds the caret offset at the beginning of the next object
-        using the given predicate as a pattern to match or not match.
-
-        Arguments:
-        -pred: a python callable that takes an accessible argument and
-               returns true/false based on some match criteria
-        -wrap: if True and the bottom of the document is reached, move
-               to the top and keep looking.
-        -currentObj: the object from which the search should begin
-
-        Returns: [obj, wrapped] where wrapped is a boolean reflecting
-        whether wrapping took place.
-        """
-
-        if not currentObj:
-            [currentObj, characterOffset] = self.getCaretContext()
-        ancestors = []
-        obj = currentObj.parent
-        while obj:
-            ancestors.append(obj)
-            obj = obj.parent
-
-        wrapped = False
-        obj = self.findNextObject(currentObj)
-        if not obj and wrap:
-            documentFrame = self.getDocumentFrame()
-            obj = documentFrame[0]
-            wrapped = True
-
-        while obj:
-            if (not obj in ancestors) and pred(obj):
-                if wrapped and self.isSameObject(currentObj, obj):
-                    obj = None
-                return [obj, wrapped]
-            else:
-                obj = self.findNextObject(obj)
-                if not obj and wrap and not wrapped:
-                    documentFrame = self.getDocumentFrame()
-                    obj = documentFrame[0]
-                    wrapped = True
-
-        return [None, wrapped]
-
     ####################################################################
     #                                                                  #
     # Methods to get information about current object.                 #
@@ -6109,7 +4862,8 @@
                 break
 
             if isAria:
-                prevObj = self.findPreviousObject(firstObj)
+                documentFrame = self.getDocumentFrame()
+                prevObj = self.findPreviousObject(firstObj, documentFrame)
                 pOffset = 0
             else:
                 [prevObj, pOffset] = \
@@ -6147,7 +4901,8 @@
                 break
 
             if isAria:
-                nextObj = self.findNextObject(lastObj)
+                documentFrame = self.getDocumentFrame()
+                nextObj = self.findNextObject(lastObj, documentFrame)
                 nOffset = 0
             else:
                 [nextObj, nOffset] = self.findNextCaretInOrder(lastObj, end)
@@ -6463,15 +5218,6 @@
 
         self.setCaretContext(obj, characterOffset)
 
-        # If we're not in a table cell, reset self.lastTableCell.
-        #
-        if obj.getRole() != pyatspi.ROLE_TABLE_CELL:
-            cell = self.getAncestor(obj,
-                                    [pyatspi.ROLE_TABLE_CELL],
-                                    [pyatspi.ROLE_DOCUMENT_FRAME])
-            if not cell:
-                self.lastTableCell = [-1, -1]
-
         # If the item is a focusable list in an HTML form, we're here
         # because we've arrowed to it.  We don't want to grab focus on
         # it and trap the user in the list. The same is true for combo
@@ -6702,7 +5448,8 @@
             failureCount += 1
         if currentLine == prevLine:
             # print "find prev line still stuck", prevObj, prevOffset
-            prevObj = self.findPreviousObject(prevObj)
+            documentFrame = self.getDocumentFrame()
+            prevObj = self.findPreviousObject(prevObj, documentFrame)
             prevOffset = 0
 
         [prevObj, prevOffset] = self.findNextCaretInOrder(prevObj,
@@ -6791,7 +5538,8 @@
             failureCount += 1
         if currentLine == nextLine:
             #print "find next line still stuck", nextObj, nextOffset
-            nextObj = self.findNextObject(nextObj)
+            documentFrame = self.getDocumentFrame()
+            nextObj = self.findNextObject(nextObj, documentFrame)
             nextOffset = 0
 
         [nextObj, nextOffset] = \
@@ -6910,354 +5658,6 @@
                     action.doAction(i)
                     break
 
-    def goPreviousHeading(self, inputEvent):
-        """Go to the previous heading regardless of level."""
-        wrap = True
-        [obj, wrapped] = self.findPreviousRole([pyatspi.ROLE_HEADING], wrap)
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the top of the web page has been
-            # reached without that object being found, we "wrap" to
-            # the bottom of the page and continuing looking upwards.
-            # We need to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to bottom."))
-        if obj:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.updateBraille(obj)
-            contents = self.getObjectContentsAtOffset(obj, characterOffset)
-            self.speakContents(contents)
-        else:
-            # Translators: this is in reference to navigating HTML content
-            # by heading (e.g., <h1>).
-            #
-            speech.speak(_("No more headings."))
-
-    def goNextHeading(self, inputEvent):
-        """Go to the next heading regardless of level."""
-        wrap = True
-        [obj, wrapped] = self.findNextRole([pyatspi.ROLE_HEADING], wrap)
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the bottom of the web page has been
-            # reached without that object being found, we "wrap" to the
-            # top of the page and continuing looking downwards. We need
-            # to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to top."))
-        if obj:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.updateBraille(obj)
-            contents = self.getObjectContentsAtOffset(obj, characterOffset)
-            self.speakContents(contents)
-        else:
-            # Translators: this is in reference to navigating HTML content
-            # by heading (e.g., <h1>).
-            #
-            speech.speak(_("No more headings."))
-
-    def goPreviousHeadingAtLevel(self, inputEvent, desiredLevel):
-        """Go to the previous heading at the specified level.
-
-        Arguments:
-        - desiredLevel: the level (1-6) of the heading to locate
-        """
-
-        found = False
-        level = 0
-        [obj, characterOffset] = self.getCaretContext()
-        if obj.parent.getRole() == pyatspi.ROLE_HEADING:
-            obj = obj.parent
-        wrap = True
-        while obj and not found:
-            [obj, wrapped] = \
-                  self.findPreviousRole([pyatspi.ROLE_HEADING], wrap, obj)
-            # We should only wrap if we haven't already done so.
-            #
-            if wrapped:
-                wrap = False
-            if obj:
-                level = self.getHeadingLevel(obj)
-                if level == desiredLevel:
-                    found = True
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the top of the web page has been
-            # reached without that object being found, we "wrap" to
-            # the bottom of the page and continuing looking upwards.
-            # We need to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to bottom."))
-        if obj and found:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.updateBraille(obj)
-            contents = self.getObjectContentsAtOffset(obj, characterOffset)
-            self.speakContents(contents)
-        else:
-            # Translators: this is in reference to navigating HTML content
-            # by heading (e.g., <h1>).
-            #
-            speech.speak(_("No more headings at level %d.") % desiredLevel)
-
-    def goNextHeadingAtLevel(self, inputEvent, desiredLevel):
-        """Go to the next heading at the specified level.
-
-        Arguments:
-        - desiredLevel: the level (1-6) of the heading to locate
-        """
-
-        found = False
-        level = 0
-        [obj, characterOffset] = self.getCaretContext()
-        wrap = True
-        while obj and not found:
-            [obj, wrapped] = \
-                  self.findNextRole([pyatspi.ROLE_HEADING], wrap, obj)
-            # We should only wrap if we haven't already done so.
-            #
-            if wrapped:
-                wrap = False
-            if obj:
-                level = self.getHeadingLevel(obj)
-                if level == desiredLevel:
-                    found = True
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the bottom of the web page has been
-            # reached without that object being found, we "wrap" to the
-            # top of the page and continuing looking downwards. We need
-            # to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to top."))
-        if obj and found:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.updateBraille(obj)
-            contents = self.getObjectContentsAtOffset(obj, characterOffset)
-            self.speakContents(contents)
-        else:
-            # Translators: this is in reference to navigating HTML content
-            # by heading (e.g., <h1>).
-            #
-            speech.speak(_("No more headings at level %d.") % desiredLevel)
-
-    def goNextHeading1(self, inputEvent):
-        """Go to the next heading at the level 1."""
-        self.goNextHeadingAtLevel(inputEvent, 1)
-
-    def goPreviousHeading1(self, inputEvent):
-        """Go to the previous heading at the level 1."""
-        self.goPreviousHeadingAtLevel(inputEvent, 1)
-
-    def goNextHeading2(self, inputEvent):
-        """Go to the next heading at the level 2."""
-        self.goNextHeadingAtLevel(inputEvent, 2)
-
-    def goPreviousHeading2(self, inputEvent):
-        """Go to the previous heading at the level 2."""
-        self.goPreviousHeadingAtLevel(inputEvent, 2)
-
-    def goNextHeading3(self, inputEvent):
-        """Go to the next heading at the level 3."""
-        self.goNextHeadingAtLevel(inputEvent, 3)
-
-    def goPreviousHeading3(self, inputEvent):
-        """Go to the previous heading at the level 3."""
-        self.goPreviousHeadingAtLevel(inputEvent, 3)
-
-    def goNextHeading4(self, inputEvent):
-        """Go to the next heading at the level 4."""
-        self.goNextHeadingAtLevel(inputEvent, 4)
-
-    def goPreviousHeading4(self, inputEvent):
-        """Go to the previous heading at the level 4."""
-        self.goPreviousHeadingAtLevel(inputEvent, 4)
-
-    def goNextHeading5(self, inputEvent):
-        """Go to the next heading at the level 5."""
-        self.goNextHeadingAtLevel(inputEvent, 5)
-
-    def goPreviousHeading5(self, inputEvent):
-        """Go to the previous heading at the level 5."""
-        self.goPreviousHeadingAtLevel(inputEvent, 5)
-
-    def goNextHeading6(self, inputEvent):
-        """Go to the next heading at the level 6."""
-        self.goNextHeadingAtLevel(inputEvent, 6)
-
-    def goPreviousHeading6(self, inputEvent):
-        """Go to the previous heading at the level 6."""
-        self.goPreviousHeadingAtLevel(inputEvent, 6)
-
-    def goPreviousChunk(self, inputEvent):
-        """Go to the previous chunk/large object."""
-        wrap = True
-        [obj, wrapped] = self.findPrevByPredicate(self.__matchChunk, wrap)
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the top of the web page has been
-            # reached without that object being found, we "wrap" to
-            # the bottom of the page and continuing looking upwards.
-            # We need to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to bottom."))
-        if obj:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.updateBraille(obj)
-            self.speakContents(self.getObjectContentsAtOffset(obj,
-                                                              characterOffset))
-        else:
-            # Translators: this is for navigating HTML in a structural
-            # manner, where a 'large object' is a logical chunk of
-            # text, such as a paragraph, a list, a table, etc.
-            #
-            speech.speak(_("No more large objects."))
-
-    def goNextChunk(self, inputEvent):
-        """Go to the next chunk/large object."""
-        wrap = True
-        [obj, wrapped] = self.findNextByPredicate(self.__matchChunk, wrap)
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the bottom of the web page has been
-            # reached without that object being found, we "wrap" to the
-            # top of the page and continuing looking downwards. We need
-            # to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to top."))
-        if obj:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.updateBraille(obj)
-            self.speakContents(self.getObjectContentsAtOffset(obj,
-                                                              characterOffset))
-        else:
-            # Translators: this is for navigating HTML in a structural
-            # manner, where a 'large object' is a logical chunk of
-            # text, such as a paragraph, a list, a table, etc.
-            #
-            speech.speak(_("No more large objects."))
-
-    def goPreviousLandmark(self, inputEvent):
-        [obj, characterOffset] = self.getCaretContext()
-
-        # Try to find it using Collection first
-        success = False
-        if settings.useCollection:
-            try:
-                startobj = obj
-                found = False
-                col = self.getDocumentFrame().queryCollection()
-                # form our list of attribute strings
-                attrs = []
-                for landmark in ARIA_LANDMARKS:
-                    attrs.append('xml-roles:' + landmark)
-                # define matchRule and find it
-                stateset = pyatspi.StateSet()
-                rule = col.createMatchRule(stateset.raw(), col.MATCH_ANY,
-                             attrs, col.MATCH_ANY,
-                             "", col.MATCH_ANY,
-                             "", col.MATCH_ALL,
-                             False)
-                [obj, wrapped] = self.findPrevByMatchRule(col, rule, True, obj)
-                if obj != startobj:
-                    found = True
-                success = True
-                col.freeMatchRule(rule)
-            except NotImplementedError:
-                debug.printException(debug.LEVEL_SEVERE)
-            except:
-                debug.printException(debug.LEVEL_SEVERE)
-                col.freeMatchRule(rule)
-
-        # Do it iteratively when Collection failed or is disabled
-        if not success or not settings.useCollection:
-            [obj, wrapped] = self.findPrevByPredicate(self.__matchLandmark,
-                                                      True, obj)
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the top of the web page has been
-            # reached without that object being found, we "wrap" to
-            # the bottom of the page and continuing looking upwards.
-            # We need to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to bottom."))
-        if obj:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.updateBraille(obj)
-            self.speakContents(self.getObjectContentsAtOffset(obj,
-                                                              characterOffset))
-        else:
-            # Translators: this is for navigating to the previous ARIA
-            # role landmark.  ARIA role landmarks are the W3C defined HTML
-            # tag attribute 'role' used to identify important part of
-            # webpage like banners, main context, search etc.  This is an
-            # that one was not found.
-            #
-            speech.speak(_("No landmark found."))
-
-    def goNextLandmark(self, inputEvent):
-        [obj, characterOffset] = self.getCaretContext()
-
-        # Try to find it using Collection first
-        success = False
-        if settings.useCollection:
-            try:
-                startobj = obj
-                col = self.getDocumentFrame().queryCollection()
-                # form our list of attribute strings
-                attrs = []
-                for landmark in ARIA_LANDMARKS:
-                    attrs.append('xml-roles:' + landmark)
-                # define matchRule and find it
-                stateset = pyatspi.StateSet()
-                rule = col.createMatchRule(stateset.raw(), col.MATCH_ANY,
-                             attrs, col.MATCH_ANY,
-                             "", col.MATCH_ANY,
-                             "", col.MATCH_ALL,
-                             False)
-                [obj, wrapped] = self.findNextByMatchRule(col, rule, True, obj)
-                if obj and obj != startobj:
-                    found = True
-                else:
-                    found = False
-                success = True
-                col.freeMatchRule(rule)
-            except NotImplementedError:
-                debug.printException(debug.LEVEL_SEVERE)
-            except:
-                debug.printException(debug.LEVEL_SEVERE)
-                col.freeMatchRule(rule)
-
-        # Do it iteratively when Collection failed or is disabled
-        if not success or not settings.useCollection:
-            [obj, wrapped] = self.findNextByPredicate(self.__matchLandmark,
-                                                      True, obj)
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the bottom of the web page has been
-            # reached without that object being found, we "wrap" to the
-            # top of the page and continuing looking downwards. We need
-            # to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to top."))
-        if obj:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.updateBraille(obj)
-            self.speakContents(self.getObjectContentsAtOffset(obj,
-                                                              characterOffset))
-        else:
-            # Translators: this is for navigating to the next ARIA
-            # role landmark. This is an that one was not found.
-            #
-            speech.speak(_("No landmark found."))
-
     def goPreviousObjectInOrder(self, inputEvent):
         """Go to the previous object in order, regardless of type or size."""
 
@@ -7414,1343 +5814,6 @@
             objectContents = [objectContents[0]]
             self.speakContents(objectContents)
 
-    def goPreviousList(self, inputEvent):
-        """Go to the previous (un)ordered list."""
-        [obj, characterOffset] = self.getCaretContext()
-        found = False
-        wrap = True
-        success = False
-
-        # Try to find it using Collection first
-        if settings.useCollection:
-            try:
-                startobj = obj
-                col = self.getDocumentFrame().queryCollection()
-                stateset = pyatspi.StateSet()
-                stateset.add(pyatspi.STATE_FOCUSABLE)
-                rule = col.createMatchRule(stateset.raw(), col.MATCH_NONE,
-                             "", col.MATCH_ANY,
-                             [pyatspi.ROLE_LIST], col.MATCH_ANY,
-                             "", col.MATCH_ALL,
-                             False)
-                [obj, wrapped] = self.findPrevByMatchRule(col,
-                                          rule, True, obj, allowNesting = True)
-                if obj and obj != startobj:
-                    found = True
-                else:
-                    found = False
-                success = True
-                col.freeMatchRule(rule)
-            except NotImplementedError:
-                debug.printException(debug.LEVEL_SEVERE)
-            except:
-                debug.printException(debug.LEVEL_SEVERE)
-                col.freeMatchRule(rule)
-
-        # Do it iteratively when Collection failed or is disabled
-        if not success or not settings.useCollection:
-            while obj and not found:
-                [obj, wrapped] = \
-                    self.findPreviousRole([pyatspi.ROLE_LIST], wrap, obj)
-                # We should only wrap if we haven't already done so.
-                #
-                if wrapped:
-                    wrap = False
-
-                # We need to be sure that the list in question is an (un)ordered
-                # list rather than a list in a form field. Form field lists are
-                # focusable; (un)ordered lists are not.
-                #
-                if obj and not obj.getState().contains(pyatspi.STATE_FOCUSABLE):
-                    found = True
-
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the top of the web page has been
-            # reached without that object being found, we "wrap" to
-            # the bottom of the page and continuing looking upwards.
-            # We need to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to bottom."))
-        if obj and found:
-            nItems = 0
-            for child in obj:
-                if child.getRole() == pyatspi.ROLE_LIST_ITEM:
-                    nItems += 1
-            # Translators: this represents a list in HTML.
-            #
-            itemString = ngettext("List with %d item",
-                                  "List with %d items",
-                                  nItems) % nItems
-            speech.speak(itemString)
-            nestingLevel = 0
-            parent = obj.parent
-            while parent.getRole() == pyatspi.ROLE_LIST:
-                nestingLevel += 1
-                parent = parent.parent
-            if nestingLevel:
-                # Translators: this represents a list item in HTML.
-                # The nesting level is how 'deep' the item is (e.g.,
-                # a level of 2 represents a list item inside a list
-                # that's inside another list).
-                #
-                speech.speak(_("Nesting level %d") % nestingLevel)
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.presentLine(obj, characterOffset)
-
-        else:
-            # Translators: this is for navigating HTML content by moving
-            # from bulleted/numbered list to bulleted/numbered list.
-            #
-            speech.speak(_("No more lists."))
-
-    def goNextList(self, inputEvent):
-        """Go to the next (un)ordered list."""
-        [obj, characterOffset] = self.getCaretContext()
-        found = False
-        wrap = True
-        success = False
-
-        # Try to find it using Collection first
-        if settings.useCollection:
-            try:
-                startobj = obj
-                col = self.getDocumentFrame().queryCollection()
-                stateset = pyatspi.StateSet()
-                stateset.add(pyatspi.STATE_FOCUSABLE)
-                rule = col.createMatchRule(stateset.raw(), col.MATCH_NONE,
-                             "", col.MATCH_ANY,
-                             [pyatspi.ROLE_LIST], col.MATCH_ANY,
-                             "", col.MATCH_ALL,
-                             False)
-                [obj, wrapped] = self.findNextByMatchRule(col, rule, True, obj)
-                if obj and obj != startobj:
-                    found = True
-                else:
-                    found = False
-                success = True
-                col.freeMatchRule(rule)
-            except NotImplementedError:
-                debug.printException(debug.LEVEL_SEVERE)
-            except:
-                debug.printException(debug.LEVEL_SEVERE)
-                col.freeMatchRule(rule)
-
-        # Do it iteratively when Collection failed or is disabled
-        if not success or not settings.useCollection:
-            while obj and not found:
-                [obj, wrapped] = \
-                    self.findNextRole([pyatspi.ROLE_LIST], wrap, obj)
-                # We should only wrap if we haven't already done so.
-                #
-                if wrapped:
-                    wrap = False
-
-                # We need to be sure that the list in question is an (un)ordered
-                # list rather than a list in a form field. Form field lists are
-                # focusable; (un)ordered lists are not.
-                #
-                if obj and not obj.getState().contains(pyatspi.STATE_FOCUSABLE):
-                    found = True
-
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the bottom of the web page has been
-            # reached without that object being found, we "wrap" to the
-            # top of the page and continuing looking downwards. We need
-            # to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to top."))
-        if obj and found:
-            nItems = 0
-            for child in obj:
-                if child.getRole() == pyatspi.ROLE_LIST_ITEM:
-                    nItems += 1
-            # Translators: this represents a list in HTML.
-            #
-            itemString = ngettext("List with %d item",
-                                  "List with %d items",
-                                  nItems) % nItems
-            speech.speak(itemString)
-            nestingLevel = 0
-            parent = obj.parent
-            while parent.getRole() == pyatspi.ROLE_LIST:
-                nestingLevel += 1
-                parent = parent.parent
-            if nestingLevel:
-                # Translators: this represents a list item in HTML.
-                # The nesting level is how 'deep' the item is (e.g.,
-                # a level of 2 represents a list item inside a list
-                # that's inside another list).
-                #
-                speech.speak(_("Nesting level %d") % nestingLevel)
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.presentLine(obj, characterOffset)
-
-        else:
-            # Translators: this is for navigating HTML content by moving
-            # from bulleted/numbered list to bulleted/numbered list.
-            #
-            speech.speak(_("No more lists."))
-
-    def goPreviousListItem(self, inputEvent):
-        """Go to the previous item in an (un)ordered list."""
-        [obj, characterOffset] = self.getCaretContext()
-        found = False
-        wrap = True
-        success = False
-
-        # Try to find it using Collection first
-        if settings.useCollection:
-            try:
-                startobj = obj
-                col = self.getDocumentFrame().queryCollection()
-                stateset = pyatspi.StateSet()
-                stateset.add(pyatspi.STATE_FOCUSABLE)
-                rule = col.createMatchRule(stateset.raw(), col.MATCH_NONE,
-                             "", col.MATCH_ANY,
-                             [pyatspi.ROLE_LIST_ITEM], col.MATCH_ANY,
-                             "", col.MATCH_ALL,
-                             False)
-                [obj, wrapped] = self.findPrevByMatchRule(col, rule, True, obj)
-                if obj and obj != startobj:
-                    found = True
-                else:
-                    found = False
-                success = True
-                col.freeMatchRule(rule)
-            except NotImplementedError:
-                debug.printException(debug.LEVEL_SEVERE)
-            except:
-                debug.printException(debug.LEVEL_SEVERE)
-                col.freeMatchRule(rule)
-
-        # Do it iteratively when Collection failed or is disabled
-        if not success or not settings.useCollection:
-            while obj and not found:
-                [obj, wrapped] = \
-                    self.findPreviousRole([pyatspi.ROLE_LIST_ITEM], wrap, obj)
-                # We should only wrap if we haven't already done so.
-                #
-                if wrapped:
-                    wrap = False
-
-                # We need to be sure that the list item in question is the
-                # child of an (un)ordered list rather than a list in a form
-                # field. Form field list items are focusable; (un)ordered
-                # list items are not.
-                #
-                if obj and \
-                not (obj.getState().contains(pyatspi.STATE_FOCUSABLE)):
-                    found = True
-
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the top of the web page has been
-            # reached without that object being found, we "wrap" to
-            # the bottom of the page and continuing looking upwards.
-            # We need to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to bottom."))
-        if obj and found:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.presentLine(obj, characterOffset)
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from bulleted/numbered list item to
-            # bulleted/numbered list item.
-            #
-            speech.speak(_("No more list items."))
-
-    def goNextListItem(self, inputEvent):
-        """Go to the next item in an (un)ordered list."""
-        [obj, characterOffset] = self.getCaretContext()
-        found = False
-        wrap = True
-        success = False
-
-        # Try to find it using Collection first
-        if settings.useCollection:
-            try:
-                startobj = obj
-                col = self.getDocumentFrame().queryCollection()
-                stateset = pyatspi.StateSet()
-                stateset.add(pyatspi.STATE_FOCUSABLE)
-                rule = col.createMatchRule(stateset.raw(), col.MATCH_NONE,
-                             "", col.MATCH_ANY,
-                             [pyatspi.ROLE_LIST_ITEM], col.MATCH_ANY,
-                             "", col.MATCH_ALL,
-                             False)
-                [obj, wrapped] = self.findNextByMatchRule(col, rule, True, obj)
-                if obj and obj != startobj:
-                    found = True
-                else:
-                    found = False
-                success = True
-                col.freeMatchRule(rule)
-            except NotImplementedError:
-                debug.printException(debug.LEVEL_SEVERE)
-            except:
-                debug.printException(debug.LEVEL_SEVERE)
-                col.freeMatchRule(rule)
-
-        # Do it iteratively when Collection failed or is disabled
-        if not success or not settings.useCollection:
-            while obj and not found:
-                [obj, wrapped] = \
-                    self.findNextRole([pyatspi.ROLE_LIST_ITEM], wrap, obj)
-                # We should only wrap if we haven't already done so.
-                #
-                if wrapped:
-                    wrap = False
-
-                # We need to be sure that the list item in question is the
-                # child of an (un)ordered list rather than a list in a form
-                # field. Form field list items are focusable; (un)ordered
-                # list items are not.
-                #
-                if obj and \
-                not (obj.getState().contains(pyatspi.STATE_FOCUSABLE)):
-                    found = True
-
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the bottom of the web page has been
-            # reached without that object being found, we "wrap" to the
-            # top of the page and continuing looking downwards. We need
-            # to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to top."))
-        if obj and found:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.presentLine(obj, characterOffset)
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from bulleted/numbered list item to
-            # bulleted/numbered list item.
-            #
-            speech.speak(_("No more list items."))
-
-    def goPreviousUnvisitedLink(self, inputEvent):
-        """Go to the previous unvisited link."""
-
-        # If the currentObject has a link in its ancestry, we've
-        # already started out on a link and need to move off of
-        # it else we'll get stuck.
-        #
-        [obj, characterOffset] = self.getCaretContext()
-        containingLink = self.getAncestor(obj,
-                                          [pyatspi.ROLE_LINK],
-                                          [pyatspi.ROLE_DOCUMENT_FRAME])
-        if containingLink:
-            obj = containingLink
-
-        success = False
-        wrap = True
-        found = False
-
-        # Try to find it using Collection first
-        if settings.useCollection:
-            try:
-                startobj = obj
-                col = self.getDocumentFrame().queryCollection()
-                stateset = pyatspi.StateSet()
-                stateset.add(pyatspi.STATE_VISITED)
-                rule = col.createMatchRule(stateset.raw(), col.MATCH_NONE,
-                             "", col.MATCH_ANY,
-                             [pyatspi.ROLE_LINK], col.MATCH_ANY,
-                             "", col.MATCH_ALL,
-                             False)
-                [obj, wrapped] = self.findPrevByMatchRule(col, rule, True, obj)
-                if obj and obj != startobj:
-                    found = True
-                else:
-                    found = False
-                success = True
-                col.freeMatchRule(rule)
-            except NotImplementedError:
-                debug.printException(debug.LEVEL_SEVERE)
-            except:
-                debug.printException(debug.LEVEL_SEVERE)
-                col.freeMatchRule(rule)
-
-        # Do it iteratively when Collection failed or is disabled
-        if not success or not settings.useCollection:
-
-            while obj and not found:
-                [obj, wrapped] = \
-                    self.findPreviousRole([pyatspi.ROLE_LINK], wrap, obj)
-                # We should only wrap if we haven't already done so.
-                #
-                if wrapped:
-                    wrap = False
-                if obj and \
-                not obj.getState().contains(pyatspi.STATE_VISITED):
-                    found = True
-
-        if wrapped or not wrap:
-            # Translators: when the user is attempting to locate a
-            # particular object and the top of the web page has been
-            # reached without that object being found, we "wrap" to
-            # the bottom of the page and continuing looking upwards.
-            # We need to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to bottom."))
-        if obj and found:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.updateBraille(obj)
-            self.speakContents(self.getObjectContentsAtOffset(obj,
-                                                              characterOffset))
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from link to link.
-            #
-            speech.speak(_("No more unvisited links."))
-
-    def goNextUnvisitedLink(self, inputEvent):
-        """Go to the next unvisited link."""
-        [obj, characterOffset] = self.getCaretContext()
-
-        success = False
-        wrap = True
-        found = False
-
-        # Try to find it using Collection first
-        if settings.useCollection:
-            try:
-                startobj = obj
-                col = self.getDocumentFrame().queryCollection()
-                stateset = pyatspi.StateSet()
-                stateset.add(pyatspi.STATE_VISITED)
-                rule = col.createMatchRule(stateset.raw(), col.MATCH_NONE,
-                             "", col.MATCH_ANY,
-                             [pyatspi.ROLE_LINK], col.MATCH_ANY,
-                             "", col.MATCH_ALL,
-                             False)
-                [obj, wrapped] = self.findNextByMatchRule(col, rule, True, obj)
-                if obj and obj != startobj:
-                    found = True
-                else:
-                    found = False
-                success = True
-                col.freeMatchRule(rule)
-            except NotImplementedError:
-                debug.printException(debug.LEVEL_SEVERE)
-            except AttributeError:
-                debug.printException(debug.LEVEL_SEVERE)
-                col.freeMatchRule(rule)
-
-        # Do it iteratively when Collection failed or is disabled
-        if not success or not settings.useCollection:
-            while obj and not found:
-                [obj, wrapped] = \
-                    self.findNextRole([pyatspi.ROLE_LINK], wrap, obj)
-                # We should only wrap if we haven't already done so.
-                #
-                if wrapped:
-                    wrap = False
-                if obj and \
-                not obj.getState().contains(pyatspi.STATE_VISITED):
-                    found = True
-
-        if wrapped or not wrap:
-            # Translators: when the user is attempting to locate a
-            # particular object and the bottom of the web page has been
-            # reached without that object being found, we "wrap" to the
-            # top of the page and continuing looking downwards. We need
-            # to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to top."))
-        if obj and found:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.updateBraille(obj)
-            self.speakContents(self.getObjectContentsAtOffset(obj,
-                                                              characterOffset))
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from link to link.
-            #
-            speech.speak(_("No more unvisited links."))
-
-    def goPreviousVisitedLink(self, inputEvent):
-        """Go to the previous visited link."""
-
-        # If the currentObject has a link in its ancestry, we've
-        # already started out on a link and need to move off of
-        # it else we'll get stuck.
-        #
-        [obj, characterOffset] = self.getCaretContext()
-        containingLink = self.getAncestor(obj,
-                                          [pyatspi.ROLE_LINK],
-                                          [pyatspi.ROLE_DOCUMENT_FRAME])
-        if containingLink:
-            obj = containingLink
-
-        success = False
-        wrap = True
-        found = False
-
-        # Try to find it using Collection first
-        if settings.useCollection:
-            try:
-                startobj = obj
-                col = self.getDocumentFrame().queryCollection()
-                stateset = pyatspi.StateSet()
-                stateset.add(pyatspi.STATE_VISITED)
-                rule = col.createMatchRule(stateset.raw(), col.MATCH_ANY,
-                             "", col.MATCH_ANY,
-                             [pyatspi.ROLE_LINK], col.MATCH_ANY,
-                             "", col.MATCH_ALL,
-                             False)
-                [obj, wrapped] = self.findPrevByMatchRule(col, rule, True, obj)
-                if obj and obj != startobj:
-                    found = True
-                else:
-                    found = False
-                success = True
-                col.freeMatchRule(rule)
-            except NotImplementedError:
-                debug.printException(debug.LEVEL_SEVERE)
-            except:
-                debug.printException(debug.LEVEL_SEVERE)
-                col.freeMatchRule(rule)
-
-        # Do it iteratively when Collection failed or is disabled
-        if not success or not settings.useCollection:
-            while obj and not found:
-                [obj, wrapped] = \
-                    self.findPreviousRole([pyatspi.ROLE_LINK], wrap, obj)
-                # We should only wrap if we haven't already done so.
-                #
-                if wrapped:
-                    wrap = False
-                if obj and \
-                obj.getState().contains(pyatspi.STATE_VISITED):
-                    found = True
-
-        if wrapped or not wrap:
-            # Translators: when the user is attempting to locate a
-            # particular object and the top of the web page has been
-            # reached without that object being found, we "wrap" to
-            # the bottom of the page and continuing looking upwards.
-            # We need to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to bottom."))
-        if obj and found:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.updateBraille(obj)
-            self.speakContents(self.getObjectContentsAtOffset(obj,
-                                                              characterOffset))
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from link to link.
-            #
-            speech.speak(_("No more visited links."))
-
-    def goNextVisitedLink(self, inputEvent):
-        """Go to the next visited link."""
-        [obj, characterOffset] = self.getCaretContext()
-
-        success = False
-        found = False
-        wrap = True
-
-        # Try to find it using Collection first
-        if settings.useCollection:
-            try:
-                startobj = obj
-                col = self.getDocumentFrame().queryCollection()
-                stateset = pyatspi.StateSet()
-                stateset.add(pyatspi.STATE_VISITED)
-                rule = col.createMatchRule(stateset.raw(), col.MATCH_ANY,
-                             "", col.MATCH_ANY,
-                             [pyatspi.ROLE_LINK], col.MATCH_ANY,
-                             "", col.MATCH_ALL,
-                             False)
-                [obj, wrapped] = self.findNextByMatchRule(col, rule, True, obj)
-                if obj and obj != startobj:
-                    found = True
-                else:
-                    found = False
-                success = True
-                col.freeMatchRule(rule)
-            except NotImplementedError:
-                debug.printException(debug.LEVEL_SEVERE)
-            except:
-                debug.printException(debug.LEVEL_SEVERE)
-                col.freeMatchRule(rule)
-
-        # Do it iteratively when Collection failed or is disabled
-        if not success or not settings.useCollection:
-            while obj and not found:
-                [obj, wrapped] = \
-                    self.findNextRole([pyatspi.ROLE_LINK], wrap, obj)
-                # We should only wrap if we haven't already done so.
-                #
-                if wrapped:
-                    wrap = False
-
-                if obj and \
-                obj.getState().contains(pyatspi.STATE_VISITED):
-                    found = True
-
-        if wrapped or not wrap:
-            # Translators: when the user is attempting to locate a
-            # particular object and the bottom of the web page has been
-            # reached without that object being found, we "wrap" to the
-            # top of the page and continuing looking downwards. We need
-            # to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to top."))
-        if obj and found:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.updateBraille(obj)
-            self.speakContents(self.getObjectContentsAtOffset(obj,
-                                                              characterOffset))
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from link to link.
-            #
-            speech.speak(_("No more visited links."))
-
-    def goPreviousBlockquote(self, inputEvent):
-        """Go to the previous blockquote."""
-
-        # If the current object has a blockquote in its ancestry, we
-        # need to move out of it else we'll get stuck.
-        #
-        [obj, characterOffset] = self.getCaretContext()
-        candidate = obj
-        while candidate and (candidate != candidate.parent) and \
-              (candidate.getRole() != pyatspi.ROLE_DOCUMENT_FRAME):
-            if self.isBlockquote(candidate):
-                obj = candidate
-                break
-            else:
-                candidate = candidate.parent
-
-        # Try to find it using Collection first
-        success = False
-        if settings.useCollection:
-            try:
-                startobj = obj
-                found = False
-                col = self.getDocumentFrame().queryCollection()
-                stateset = pyatspi.StateSet()
-                rule = col.createMatchRule(stateset.raw(), col.MATCH_ANY,
-                             ['tag:BLOCKQUOTE'], col.MATCH_ANY,
-                             "", col.MATCH_ANY,
-                             "", col.MATCH_ALL,
-                             False)
-                [obj, wrapped] = self.findPrevByMatchRule(col, rule, True, obj)
-                if obj != startobj:
-                    found = True
-                success = True
-                col.freeMatchRule(rule)
-            except NotImplementedError:
-                debug.printException(debug.LEVEL_SEVERE)
-            except:
-                debug.printException(debug.LEVEL_SEVERE)
-                col.freeMatchRule(rule)
-
-        # Do it iteratively when Collection failed or is disabled
-        if not success or not settings.useCollection:
-            currentObj = obj
-            found = False
-            wrapped = False
-            wrap = True
-            while obj and not found:
-                obj = self.findPreviousObject(obj)
-                if not obj and wrap and not wrapped:
-                    obj = self.getLastObject()
-                    wrapped = True
-                if obj and self.isBlockquote(obj) and \
-                not self.isSameObject(currentObj, obj):
-                    found = True
-
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the top of the web page has been
-            # reached without that object being found, we "wrap" to
-            # the bottom of the page and continuing looking upwards.
-            # We need to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to bottom."))
-        if obj and found:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.presentLine(obj, characterOffset)
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from blockquote to blockquote.
-            #
-            speech.speak(_("No more blockquotes."))
-
-    def goNextBlockquote(self, inputEvent):
-        """Go to the next blockquote."""
-        [obj, characterOffset] = self.getCaretContext()
-
-        # get the ancestors because setCaretPosition may drop us lower
-        # in the tree.  This is used to check that the start object is not
-        # the same as the found object.
-        ancestors = []
-        ancestor = obj
-        while ancestor:
-            ancestors.append(ancestor)
-            ancestor = ancestor.parent
-
-        # Try to find it using Collection first
-        success = False
-        if settings.useCollection:
-            try:
-                startobj = obj
-                found = False
-                col = self.getDocumentFrame().queryCollection()
-                stateset = pyatspi.StateSet()
-                rule = col.createMatchRule(stateset.raw(), col.MATCH_ANY,
-                             ['tag:BLOCKQUOTE'], col.MATCH_ANY,
-                             "", col.MATCH_ANY,
-                             "", col.MATCH_ALL,
-                             False)
-                [obj, wrapped] = self.findNextByMatchRule(col, rule, True, obj)
-                if obj and obj not in ancestors:
-                    found = True
-                success = True
-                col.freeMatchRule(rule)
-            except NotImplementedError:
-                debug.printException(debug.LEVEL_SEVERE)
-            except:
-                debug.printException(debug.LEVEL_SEVERE)
-                col.freeMatchRule(rule)
-
-        # Do it iteratively when Collection failed or is disabled
-        if not success or not settings.useCollection:
-            currentObj = obj
-            found = False
-            wrapped = False
-            wrap = True
-            while obj and not found:
-                obj = self.findNextObject(obj)
-                if not obj and wrap and not wrapped:
-                    documentFrame = self.getDocumentFrame()
-                    obj = documentFrame[0]
-                    wrapped = True
-                if obj and self.isBlockquote(obj) and \
-                not self.isSameObject(currentObj, obj):
-                    found = True
-
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the bottom of the web page has been
-            # reached without that object being found, we "wrap" to the
-            # top of the page and continuing looking downwards. We need
-            # to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to top."))
-        if obj and found:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.presentLine(obj, characterOffset)
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from blockquote to blockquote.
-            #
-            speech.speak(_("No more blockquotes."))
-
-    def goPreviousFormField(self, inputEvent):
-        """Go to the previous form field."""
-
-        # If the current object is a list item in a form field, we
-        # need to move up to the parent list before we search
-        # for the previous list; otherwise we'll find the current
-        # list first.
-        #
-        [obj, characterOffset] = self.getCaretContext()
-        currentObj = obj
-        if obj.getRole() == pyatspi.ROLE_LIST_ITEM and \
-           obj.getState().contains(pyatspi.STATE_FOCUSABLE):
-            obj = obj.parent
-
-        found = False
-        wrapped = False
-        wrap = True
-        while obj and not found:
-            obj = self.findPreviousObject(obj)
-            if not obj and wrap and not wrapped:
-                obj = self.getLastObject()
-                wrapped = True
-            if obj:
-                # For performance purposes, we do not want to work
-                # our way up through list items in search of the
-                # parent list.
-                #
-                if obj.getRole() == pyatspi.ROLE_LIST_ITEM:
-                    obj = obj.parent
-                found = self.isFormField(obj)
-                if wrapped and self.isSameObject(currentObj, obj):
-                    obj = None
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the top of the web page has been
-            # reached without that object being found, we "wrap" to
-            # the bottom of the page and continuing looking upwards.
-            # We need to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to bottom."))
-        if obj:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            # We actively avoid grabbing focus on lists in HTML forms
-            # in setCaretPosition() so that a user doesn't accidentally
-            # arrow into one and change its value.  Here we actually
-            # do want to grab focus should we be on a list or combo box.
-            #
-            if obj.getRole() in [pyatspi.ROLE_LIST, pyatspi.ROLE_COMBO_BOX]:
-                obj.queryComponent().grabFocus()
-            else:
-                self.setCaretPosition(obj, characterOffset)
-                self.updateBraille(obj)
-                self.speakContents(self.getObjectContentsAtOffset(obj,
-                                                             characterOffset))
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from form field to form field.
-            #
-            speech.speak(_("No more form fields."))
-
-    def goNextFormField(self, inputEvent):
-        """Go to the next form field."""
-        [obj, characterOffset] = self.getCaretContext()
-        currentObj = obj
-        found = False
-        wrapped = False
-        wrap = True
-        # For performance purposes, we do not want to examine each item
-        # in the current combo box or list via findNextObject.
-        #
-        if obj.getRole() == pyatspi.ROLE_LIST_ITEM \
-           and obj.getState().contains(pyatspi.STATE_FOCUSABLE):
-            obj = obj.parent
-        if obj.getRole() == pyatspi.ROLE_LIST \
-           and obj.getState().contains(pyatspi.STATE_FOCUSABLE):
-            obj = obj[obj.childCount - 1]
-        elif obj.getRole() == pyatspi.ROLE_COMBO_BOX:
-            menu = obj[0]
-            obj = menu[menu.childCount - 1]
-
-        while obj and not found:
-            obj = self.findNextObject(obj)
-            if not obj and wrap and not wrapped:
-                documentFrame = self.getDocumentFrame()
-                obj = documentFrame[0]
-                wrapped = True
-            if obj:
-                found = self.isFormField(obj)
-                if wrapped and self.isSameObject(currentObj, obj):
-                    obj = None
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the bottom of the web page has been
-            # reached without that object being found, we "wrap" to the
-            # top of the page and continuing looking downwards. We need
-            # to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to top."))
-        if obj:
-            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-
-            # We actively avoid grabbing focus on lists in HTML forms
-            # in setCaretPosition() so that a user doesn't accidentally
-            # arrow into one and change its value.  Here we actually
-            # do want to grab focus should we be on a list or combo box.
-            #
-            if obj.getRole() in [pyatspi.ROLE_LIST, pyatspi.ROLE_COMBO_BOX]:
-                obj.queryComponent().grabFocus()
-            else:
-                self.setCaretPosition(obj, characterOffset)
-                self.updateBraille(obj)
-                self.speakContents(self.getObjectContentsAtOffset(obj,
-                                                             characterOffset))
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from form field to form field.
-            #
-            speech.speak(_("No more form fields."))
-
-    def moveToCell(self, obj):
-        """Move to the specified cell in an HTML table.
-
-        Arguments:
-        - obj: the table cell to move to.
-        """
-        spanString = self.getCellSpanInfo (obj)
-        blank = self.isBlankCell(obj)
-        [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-        self.setCaretPosition(obj, characterOffset)
-        self.updateBraille(obj)
-        if not blank:
-            self.speakContents(self.getObjectContentsAtOffset(obj,
-                                                             characterOffset))
-        else:
-            # Translators: "blank" is a short word to mean the
-            # user has navigated to an empty line.
-            #
-            speech.speak(_("blank"))
-
-        if speakCellCoordinates:
-            [row, col] = self.getCellCoordinates(obj)
-            # Translators: this represents the (row, col) position of
-            # a cell in a table.
-            #
-            speech.speak(_("Row %d, column %d.") % (row + 1, col + 1))
-
-        if spanString and speakCellSpan:
-            speech.speak(spanString)
-
-    def goPreviousTable(self, inputEvent):
-        """Go to the previous table."""
-        wrap = True
-        [obj, wrapped] = self.findPreviousRole([pyatspi.ROLE_TABLE], wrap)
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the top of the web page has been
-            # reached without that object being found, we "wrap" to
-            # the bottom of the page and continuing looking upwards.
-            # We need to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to bottom."))
-        if obj:
-            table = obj.queryTable()
-            caption = self.getTableCaption(obj)
-            if self.queryNonEmptyText(caption):
-                text = self.getDisplayedText(caption)
-                speech.speak(text)
-            nonUniformString = ""
-            nonUniform = self.isNonUniformTable(obj)
-            if nonUniform:
-                # Translators: a uniform table is one in which each table
-                # cell occupies one row and one column (i.e. a perfect grid)
-                # In contrast, a non-uniform table is one in which at least
-                # one table cell occupies more than one row and/or column.
-                #
-                nonUniformString = _("Non-uniform")
-            nRows = table.nRows
-            nColumns = table.nColumns
-            # Translators: this represents the number of rows in an HTML table.
-            #
-            rowString = ngettext("Table with %d row",
-                                 "Table with %d rows",
-                                  nRows) % nRows
-            # Translators: this represents the number of cols in an HTML table.
-            #
-            colString = ngettext("%d column",
-                                 "%d columns",
-                                  nColumns) % nColumns
-            speech.speak(nonUniformString + " " + rowString + " " + colString)
-
-            obj = table.getAccessibleAt(0, 0)
-            self.moveToCell(obj)
-
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from table to table.
-            #
-            speech.speak(_("No more tables."))
-
-    def goNextTable(self, inputEvent):
-        """Go to the next table."""
-        wrap = True
-        [obj, wrapped] = self.findNextRole([pyatspi.ROLE_TABLE], wrap)
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the bottom of the web page has been
-            # reached without that object being found, we "wrap" to the
-            # top of the page and continuing looking downwards. We need
-            # to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to top."))
-        if obj:
-            table = obj.queryTable()
-            caption = self.getTableCaption(obj)
-            if self.queryNonEmptyText(caption):
-                text = self.getDisplayedText(caption)
-                speech.speak(text)
-            nonUniformString = ""
-            nonUniform = self.isNonUniformTable(obj)
-            if nonUniform:
-                # Translators: a uniform table is one in which each table
-                # cell occupies one row and one column (i.e. a perfect grid)
-                # In contrast, a non-uniform table is one in which at least
-                # one table cell occupies more than one row and/or column.
-                #
-                nonUniformString = _("Non-uniform")
-            nRows = table.nRows
-            nColumns = table.nColumns
-            # Translators: this represents the number of rows in an HTML table.
-            #
-            rowString = ngettext("Table with %d row",
-                                 "Table with %d rows",
-                                  nRows) % nRows
-            # Translators: this represents the number of cols in an HTML table.
-            #
-            colString = ngettext("%d column",
-                                 "%d columns",
-                                  nColumns) % nColumns
-            speech.speak(nonUniformString + " " + rowString + " " + colString)
-            obj = table.getAccessibleAt(0, 0)
-            self.moveToCell(obj)
-
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from table to table.
-            #
-            speech.speak(_("No more tables."))
-
-    def goCellLeft(self, inputEvent):
-        """Move to the cell on the left in an HTML table."""
-        [obj, characterOffset] = self.getCaretContext()
-        if obj.getRole() != pyatspi.ROLE_TABLE_CELL:
-            obj = self.getAncestor(obj,
-                                   [pyatspi.ROLE_TABLE_CELL],
-                                   [pyatspi.ROLE_DOCUMENT_FRAME])
-        if obj:
-            [row, col] = self.getCellCoordinates(obj)
-            oldHeaders = self.getColumnHeaders(obj)
-            table = obj.parent.queryTable()
-            if self.isSameCell(obj.parent,
-                               [row, col],
-                               self.lastTableCell):
-                # The stored row helps us maintain the correct position
-                # when traversing cells that span multiple rows.
-                #
-                row = self.lastTableCell[0]
-            found = False
-            while not found and col > 0:
-                obj = table.getAccessibleAt(row, col - 1)
-                self.lastTableCell = [row, col - 1]
-                if not self.isBlankCell(obj) or \
-                   not skipBlankCells:
-                    found = True
-                else:
-                    col -= 1
-
-            if found:
-                # We only want to speak the header information that has
-                # changed, and we don't want to speak headers if we're in
-                # a header column.
-                #
-                if speakCellHeaders and not self.isInHeaderColumn(obj):
-                    colHeaders = self.getColumnHeaders(obj)
-                    for header in colHeaders:
-                        if not header in oldHeaders:
-                            text = self.getDisplayedText(header)
-                            speech.speak(text)
-                self.moveToCell(obj)
-            else:
-                # Translators: this is for navigating HTML content by
-                # moving from table cell to table cell.
-                #
-                speech.speak(_("Beginning of row."))
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from table cell to table cell.
-            #
-            speech.speak(_("Not in a table."))
-
-    def goCellRight(self, inputEvent):
-        """Move to the cell on the right in an HTML table."""
-        [obj, characterOffset] = self.getCaretContext()
-        if obj.getRole() != pyatspi.ROLE_TABLE_CELL:
-            obj = self.getAncestor(obj,
-                                   [pyatspi.ROLE_TABLE_CELL],
-                                   [pyatspi.ROLE_DOCUMENT_FRAME])
-        if obj:
-            [row, col] = self.getCellCoordinates(obj)
-            oldHeaders = self.getColumnHeaders(obj)
-            table = obj.parent.queryTable()
-            if self.isSameCell(obj.parent,
-                               [row, col],
-                               self.lastTableCell):
-                # The stored row helps us maintain the correct position
-                # when traversing cells that span multiple rows.
-                #
-                row = self.lastTableCell[0]
-            colspan = table.getColumnExtentAt(row, col)
-            nextCol = col + colspan
-            found = False
-            while not found and (nextCol <= table.nColumns - 1):
-                obj = table.getAccessibleAt(row, nextCol)
-                self.lastTableCell = [row, nextCol]
-                if not self.isBlankCell(obj) or \
-                   not skipBlankCells:
-                    found = True
-                else:
-                    col += 1
-                    colspan = table.getColumnExtentAt(row, col)
-                    nextCol = col + colspan
-
-            if found:
-                # We only want to speak the header information that has
-                # changed, and we don't want to speak headers if we're in
-                # a header column.
-                #
-                if speakCellHeaders and not self.isInHeaderColumn(obj):
-                    colHeaders = self.getColumnHeaders(obj)
-                    for header in colHeaders:
-                        if not header in oldHeaders:
-                            text = self.getDisplayedText(header)
-                            speech.speak(text)
-                self.moveToCell(obj)
-            else:
-                # Translators: this is for navigating HTML content by
-                # moving from table cell to table cell.
-                #
-                speech.speak(_("End of row."))
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from table cell to table cell.
-            #
-            speech.speak(_("Not in a table."))
-
-    def goCellUp(self, inputEvent):
-        """Move one cell up in an HTML table."""
-        [obj, characterOffset] = self.getCaretContext()
-        if obj.getRole() != pyatspi.ROLE_TABLE_CELL:
-            obj = self.getAncestor(obj,
-                                   [pyatspi.ROLE_TABLE_CELL],
-                                   [pyatspi.ROLE_DOCUMENT_FRAME])
-        if obj:
-            [row, col] = self.getCellCoordinates(obj)
-            oldHeaders = self.getRowHeaders(obj)
-            table = obj.parent.queryTable()
-            if self.isSameCell(obj.parent,
-                               [row, col],
-                               self.lastTableCell):
-                # The stored column helps us maintain the correct position
-                # when traversing cells that span multiple columns.
-                #
-                col = self.lastTableCell[1]
-            found = False
-            while not found and row > 0:
-                obj = table.getAccessibleAt(row - 1, col)
-                self.lastTableCell = [row - 1, col]
-                if not self.isBlankCell(obj) or \
-                   not skipBlankCells:
-                    found = True
-                else:
-                    row -= 1
-
-            if found:
-                # We only want to speak the header information that has
-                # changed, and we don't want to speak headers if we're in
-                # a header row.
-                #
-                if speakCellHeaders and not self.isInHeaderRow(obj):
-                    rowHeaders = self.getRowHeaders(obj)
-                    for header in rowHeaders:
-                        if not header in oldHeaders:
-                            text = self.getDisplayedText(header)
-                            speech.speak(text)
-                self.moveToCell(obj)
-            else:
-                # Translators: this is for navigating HTML content by
-                # moving from table cell to table cell.
-                #
-                speech.speak(_("Top of column."))
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from table cell to table cell.
-            #
-            speech.speak(_("Not in a table."))
-
-    def goCellDown(self, inputEvent):
-        """Move one cell down in an HTML table."""
-        [obj, characterOffset] = self.getCaretContext()
-        if obj.getRole() != pyatspi.ROLE_TABLE_CELL:
-            obj = self.getAncestor(obj,
-                                   [pyatspi.ROLE_TABLE_CELL],
-                                   [pyatspi.ROLE_DOCUMENT_FRAME])
-        if obj:
-            [row, col] = self.getCellCoordinates(obj)
-            oldHeaders = self.getRowHeaders(obj)
-            table = obj.parent.queryTable()
-            if self.isSameCell(obj.parent,
-                               [row, col],
-                               self.lastTableCell):
-                # The stored column helps us maintain the correct position
-                # when traversing cells that span multiple columns.
-                #
-                col = self.lastTableCell[1]
-            rowspan = table.getRowExtentAt(row, col)
-            nextRow = row + rowspan
-            found = False
-            while not found and (nextRow <= table.nRows - 1):
-                obj = table.getAccessibleAt(nextRow, col)
-                self.lastTableCell = [nextRow, col]
-                if not self.isBlankCell(obj) or \
-                   not skipBlankCells:
-                    found = True
-                else:
-                    row += 1
-                    rowspan = table.getRowExtentAt(row, col)
-                    nextRow = row + rowspan
-
-            if found:
-                # We only want to speak the header information that has
-                # changed, and we don't want to speak headers if we're in
-                # a header row.
-                #
-                if speakCellHeaders and not self.isInHeaderRow(obj):
-                    rowHeaders = self.getRowHeaders(obj)
-                    for header in rowHeaders:
-                        if not header in oldHeaders:
-                            text = self.getDisplayedText(header)
-                            speech.speak(text)
-                self.moveToCell(obj)
-            else:
-                # Translators: this is for navigating HTML content by
-                # moving from table cell to table cell.
-                #
-                speech.speak(_("Bottom of column."))
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from table cell to table cell.
-            #
-            speech.speak(_("Not in a table."))
-
-    def goCellFirst(self, inputEvent):
-        """Move to the first cell in an HTML table."""
-        [obj, characterOffset] = self.getCaretContext()
-        if obj.getRole() != pyatspi.ROLE_TABLE:
-            obj = self.getAncestor(obj,
-                                   [pyatspi.ROLE_TABLE],
-                                   [pyatspi.ROLE_DOCUMENT_FRAME])
-        if obj:
-            obj = obj.queryTable().getAccessibleAt(0, 0)
-            self.lastTableCell = [0, 0]
-            self.moveToCell(obj)
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from table cell to table cell.
-            #
-            speech.speak(_("Not in a table."))
-
-    def goCellLast(self, inputEvent):
-        """Move to the last cell in an HTML table."""
-        [obj, characterOffset] = self.getCaretContext()
-        if obj.getRole() != pyatspi.ROLE_TABLE:
-            obj = self.getAncestor(obj,
-                                   [pyatspi.ROLE_TABLE],
-                                   [pyatspi.ROLE_DOCUMENT_FRAME])
-        if obj:
-            table = obj.queryTable()
-            lastRow = table.nRows - 1
-            lastCol = table.nColumns - 1
-            obj = table.getAccessibleAt(lastRow, lastCol)
-            self.lastTableCell = [lastRow, lastCol]
-            self.moveToCell(obj)
-        else:
-            # Translators: this is for navigating HTML content by
-            # moving from table cell to table cell.
-            #
-            speech.speak(_("Not in a table."))
-
-    def goNextLiveRegion(self, inputEvent):
-        # First, get any live regions that have been registered as LIVE_NONE
-        # because there is no markup to test for these but we still want to
-        # find them
-        regobjs = self.liveMngr.getLiveNoneObjects()
-        # define our search predicate
-        pred = lambda obj: (self.liveMngr.matchLiveRegion(obj) \
-                                            or obj in regobjs)
-        # start looking
-        wrap = True
-        [obj, wrapped] = \
-                  self.findNextByPredicate(pred, wrap)
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the bottom of the web page has been
-            # reached without that object being found, we "wrap" to the
-            # top of the page and continuing looking downwards. We need
-            # to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to top."))
-        if obj:
-            # TODO:   We don't want to move to a list item.
-            # Is this the best place to handle this?
-            if obj.getRole() == pyatspi.ROLE_LIST:
-                characterOffset = 0
-                obj = obj[0]
-            else:
-                [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.updateBraille(obj)
-            # For debugging
-            self.outlineAccessible(obj)
-            self.speakContents(self.getObjectContentsAtOffset(obj,
-                                                              characterOffset))
-        else:
-            # Translators: this is for navigating HTML in a structural
-            # manner, where a 'live region' is a location in a web page
-            # that are updated without having to refresh the entire page.
-            #
-            speech.speak(_("No more live regions."))
-
-    def goPreviousLiveRegion(self, inputEvent):
-        # First, get any live regions that have been registered as LIVE_NONE
-        # because there is no markup to test for these but we still want to
-        # find them
-        regobjs = self.liveMngr.getLiveNoneObjects()
-        # define our search predicate
-        pred = lambda obj: (self.liveMngr.matchLiveRegion(obj) \
-                                            or obj in regobjs)
-        # start looking
-        wrap = True
-        [obj, wrapped] = \
-                  self.findPrevByPredicate(pred, wrap)
-        if wrapped:
-            # Translators: when the user is attempting to locate a
-            # particular object and the bottom of the web page has been
-            # reached without that object being found, we "wrap" to the
-            # top of the page and continuing looking downwards. We need
-            # to inform the user when this is taking place.
-            #
-            speech.speak(_("Wrapping to top."))
-        if obj:
-            # TODO:   We don't want to move to a list item.
-            # Is this the best place to handle this?
-            if obj.getRole() == pyatspi.ROLE_LIST:
-                characterOffset = 0
-            else:
-                [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
-            self.setCaretPosition(obj, characterOffset)
-            self.updateBraille(obj)
-            self.outlineAccessible(obj)
-            self.speakContents(self.getObjectContentsAtOffset(obj,
-                                                              characterOffset))
-        else:
-            # Translators: this is for navigating HTML in a structural
-            # manner, where a 'live region' is a location in a web page
-            # that are updated without having to refresh the entire page.
-            #
-            speech.speak(_("No more live regions."))
-
-    def goLastLiveRegion(self, inputEvent):
-        if settings.inferLiveRegions:
-            self.liveMngr.goLastLiveRegion()
-        else:
-            # Translators: this announces to the user that live region
-            # support has been turned off.
-            #
-            speech.speak(_("Live region support is off"))
-
     def advanceLivePoliteness(self, inputEvent):
         """Advances live region politeness level."""
         if settings.inferLiveRegions:
@@ -8829,42 +5892,6 @@
         speech.speak(string)
         braille.displayMessage(string)
 
-    def toggleStructuralNavigation(self, inputEvent):
-        """Toggles structural navigation keys."""
-
-        global structuralNavigationEnabled
-
-        structuralNavigationEnabled = not structuralNavigationEnabled
-
-        if structuralNavigationEnabled:
-            # Translators: the structural navigation keys are designed
-            # to move the caret around the HTML content by object type.
-            # Thus H moves you to the next heading, Shift H to the
-            # previous heading, T to the next table, and so on. Some
-            # users prefer to turn this off to use Firefox's search
-            # when typing feature.  This message is sent to both the
-            # braille display and the speech synthesizer when the user
-            # toggles the structural navigation feature of Orca.
-            # It should be a brief informative message.
-            #
-            string = _("Structural navigation keys on.")
-        else:
-            # Translators: the structural navigation keys are designed
-            # to move the caret around the HTML content by object type.
-            # Thus H moves you to the next heading, Shift H to the
-            # previous heading, T to the next table, and so on. Some
-            # users prefer to turn this off to use Firefox's search
-            # when typing feature.  This message is sent to both the
-            # braille display and the speech synthesizer when the user
-            # toggles the structural navigation feature of Orca.
-            # It should be a brief informative message.
-            #
-            string = _("Structural navigation keys off.")
-
-        debug.println(debug.LEVEL_CONFIGURATION, string)
-        speech.speak(string)
-        braille.displayMessage(string)
-
     def speakWordUnderMouse(self, acc):
         """Determine if the speak-word-under-mouse capability applies to
         the given accessible.
@@ -8880,32 +5907,3 @@
             except NotImplementedError:
                 return True
         default.Script.speakWordUnderMouse(self, acc)
-
-    ####################################################################
-    #                                                                  #
-    # Match Predicates                                                 #
-    #                                                                  #
-    ####################################################################
-
-    def __matchChunk(self, obj):
-        if obj.getRole() in OBJECT_ROLES:
-            text = self.queryNonEmptyText(obj)
-            if text and text.characterCount > largeObjectTextLength \
-               and not self.isUselessObject(obj):
-                return True
-            else:
-                return False
-        else:
-            return False
-
-    def __matchLandmark(self, obj):
-        if obj is None:
-            return False
-        attrs = self._getAttrDictionary(obj)
-        try:
-            if attrs['xml-roles'] in ARIA_LANDMARKS:
-                return True
-            else:
-                return False
-        except KeyError:
-            return False
Index: src/orca/scripts/toolkits/Gecko/structural_navigation.py
===================================================================
--- src/orca/scripts/toolkits/Gecko/structural_navigation.py	(revision 0)
+++ src/orca/scripts/toolkits/Gecko/structural_navigation.py	(revision 0)
@@ -0,0 +1,125 @@
+# Orca
+#
+# Copyright 2005-2008 Sun Microsystems Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA  02110-1301 USA.
+
+"""Custom structural navigation for the Gecko toolkit."""
+
+__id__        = "$Id$"
+__version__   = "$Revision$"
+__date__      = "$Date$"
+__copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc."
+__license__   = "LGPL"
+
+import orca.structural_navigation as structural_navigation
+
+########################################################################
+#                                                                      #
+# Custom Structural Navigation                                         #
+#                                                                      #
+########################################################################
+
+class GeckoStructuralNavigation(structural_navigation.StructuralNavigation):
+
+    def __init__(self, script, enabledTypes, enabled):
+        """Gecko specific Structural Navigation."""
+
+        structural_navigation.StructuralNavigation.__init__(self,
+                                                            script,
+                                                            enabledTypes,
+                                                            enabled)
+
+    #####################################################################
+    #                                                                   #
+    # Methods for finding and moving to objects                         #
+    #                                                                   #
+    #####################################################################
+
+    def getCurrentObject(self):
+        """Returns the current object -- normally, the locusOfFocus. But
+        in the case of Gecko, that doesn't always work.
+        """
+        [obj, offset] = self._script.getCaretContext()
+        return obj
+
+    def _findPreviousObject(self, obj, stopAncestor):
+        """Finds the object prior to this one, where the tree we're
+        dealing with is a DOM and 'prior' means the previous object
+        in a linear presentation sense.
+
+        Arguments:
+        -obj: the object where to start.
+        -stopAncestor: the ancestor at which the search should stop
+        """
+        return self._script.findPreviousObject(obj, stopAncestor)
+
+    def _findNextObject(self, obj, stopAncestor):
+        """Finds the object after to this one, where the tree we're
+        dealing with is a DOM and 'next' means the next object
+        in a linear presentation sense.
+
+        Arguments:
+        -obj: the object where to start.
+        -stopAncestor: the ancestor at which the search should stop
+        """
+        return self._script.findNextObject(obj, stopAncestor)
+
+    def _findLastObject(self, ancestor):
+        """Returns the last object in ancestor."""
+        return self._script.getLastObject(ancestor)
+
+    def _getDocument(self):
+        """Returns the document or other object in which the object of
+        interest is contained.
+        """
+        return self._script.getDocumentFrame()
+
+    def _isInDocument(self, obj):
+        """Returns True of the object is inside of the document."""
+        return self._script.inDocumentContent(obj)
+
+    def _getCaretPosition(self, obj):
+        """Returns the [obj, characterOffset] where the caret should be
+        positioned.
+        """
+        return self._script.findFirstCaretContext(obj, 0)
+
+    def _setCaretPosition(self, obj, characterOffset):
+        """Sets the caret at the specified offset within obj."""
+        self._script.setCaretPosition(obj, characterOffset)
+
+    def _isUselessObject(self, obj):
+        """Returns true if the given object is an obj that doesn't
+        have any meaning associated with it.
+        """
+        return self._script.isUselessObject(obj)
+
+    #####################################################################
+    #                                                                   #
+    # Methods for presenting objects                                    #
+    #                                                                   #
+    #####################################################################
+
+    def _presentLine(self, obj, offset):
+        """Presents the first line of the object to the user."""
+        self._script.presentLine(obj, offset)
+
+    def _presentObject(self, obj, offset):
+        """Presents the entire object to the user."""
+        self._script.updateBraille(obj)
+        contents = self._script.getObjectContentsAtOffset(obj, offset)
+        self._script.speakContents(contents)
Index: src/orca/structural_navigation.py
===================================================================
--- src/orca/structural_navigation.py	(revision 0)
+++ src/orca/structural_navigation.py	(revision 0)
@@ -0,0 +1,2616 @@
+# Orca
+#
+# Copyright 2005-2008 Sun Microsystems Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA  02110-1301 USA.
+
+"""Implements structural navigation.  Right now this is only
+being implemented by Gecko; however it can be used in any
+script providing access to document content."""
+
+__id__        = "$Id$"
+__version__   = "$Revision$"
+__date__      = "$Date$"
+__copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc."
+__license__   = "LGPL"
+
+import pyatspi
+
+import braille
+import debug
+import input_event
+import keybindings
+import orca
+import orca_state
+import settings
+import speech
+
+from orca_i18n import _
+from orca_i18n import ngettext
+
+noModMask           =  0
+orcaModMask         =  1 << settings.MODIFIER_ORCA
+shiftModMask        =  1 << pyatspi.MODIFIER_SHIFT
+shiftAltModMask     = (1 << pyatspi.MODIFIER_SHIFT |
+                       1 << pyatspi.MODIFIER_ALT)
+orcaShiftModMask    = (1 << settings.MODIFIER_ORCA |
+                       1 << pyatspi.MODIFIER_SHIFT)
+orcaControlModMask  = (1 << settings.MODIFIER_ORCA |
+                       1 << pyatspi.MODIFIER_CONTROL)
+fullModMask         = (1 << pyatspi.MODIFIER_SHIFT |
+                       1 << pyatspi.MODIFIER_ALT |
+                       1 << pyatspi.MODIFIER_CONTROL |
+                       1 << settings.MODIFIER_ORCA)
+
+#############################################################################
+#                                                                           #
+# MatchCriteria                                                             #
+#                                                                           #
+#############################################################################
+
+class MatchCriteria:
+    """Contains the criteria which will be used to generate a collection
+    matchRule.  We don't want to create the rule until we need it and
+    are ready to use it. In addition, the creation of an AT-SPI match
+    rule requires you specify quite a few things (see the __init__),
+    most of which are irrelevant to the search at hand.  This class
+    makes it possible for the StructNavObject creator to just specify
+    the few criteria that actually matter."""
+
+    def __init__(self,
+                 col,
+                 states = [],
+                 matchStates = None,
+                 objAttrs = [],
+                 matchObjAttrs = None,
+                 roles = [],
+                 matchRoles = None,
+                 ifaces = "",
+                 matchIfaces = None,
+                 invert = False):
+
+        self.col = col
+        self.matchStates = matchStates or col.MATCH_ANY
+        self.objAttrs = objAttrs
+        self.matchObjAttrs = matchObjAttrs or col.MATCH_ANY
+        self.roles = roles
+        self.matchRoles = matchRoles or col.MATCH_ANY
+        self.ifaces = ifaces
+        self.matchIfaces = matchIfaces or col.MATCH_ALL
+        self.invert = invert
+
+        self.states = pyatspi.StateSet()
+        for state in states:
+            self.states.add(state)
+
+###########################################################################
+#                                                                         #
+# StructNavObject                                                         #
+#                                                                         #
+###########################################################################
+
+class StructNavObject:
+    """Represents a document object which has identifiable characteristics
+    which can be used for the purpose of navigation to and among instances
+    of that object. These characteristics may be something as simple as a
+    role and/or a state of interest. Or they may be something more complex
+    such as character counts, text attributes, and other object attributes.
+    """
+
+    def __init__(self, sn, objType, bindings, predicate, criteria,
+                 presentation):
+
+        """Creates a new structural navigation object.
+
+        Arguments:
+        - sn: the StructuralNavigation class associated with this object.
+        - objType: the type (e.g. BLOCKQUOTE) associated with this object.
+        - bindings: a dictionary of all of the possible bindings for this
+          object.  In the case of all but the "atLevel" bindings, each
+          binding takes the form of [keysymstring, modifiers, description].
+          The goPreviousAtLevel and goNextAtLevel bindings are each a list
+          of bindings in that form.
+        - predicate: the predicate to use to locate the sn object when
+          using collection is not possible or practical.
+        - criteria: a method which returns a MatchCriteria object which
+          can in turn be used to locate the sn object via collection.
+        - presentation: the method which should be called after
+          peforming the search for the sn object.
+        """
+
+        self.sn = sn
+        self.objType = objType
+        self.bindings = bindings
+        self.predicate = predicate
+        self.criteria = criteria
+        self.present = presentation
+
+        self.inputEventHandlers = {}
+        self.keyBindings = keybindings.KeyBindings()
+        self.functions = []
+        self._setUpHandlersAndBindings()
+
+    def _setUpHandlersAndBindings(self):
+        """Adds the inputEventHandlers and keyBindings for this object."""
+
+        # Set up the basic handlers.  These are our traditional goPrevious
+        # and goNext functions.
+        #
+        previous = self.bindings.get("previous")
+        if previous:
+            [keysymstring, modifiers, description] = previous
+            self.inputEventHandlers["goPreviousHandler"] = \
+                input_event.InputEventHandler(self.goPrevious, description)
+
+            self.keyBindings.add(
+                keybindings.KeyBinding(
+                    keysymstring,
+                    fullModMask,
+                    modifiers,
+                    self.inputEventHandlers["goPreviousHandler"]))
+
+            self.functions.append(self.goPrevious)
+
+        next = self.bindings.get("next")
+        if next:
+            [keysymstring, modifiers, description] = next
+            self.inputEventHandlers["goNextHandler"] = \
+                input_event.InputEventHandler(self.goNext, description)
+
+            self.keyBindings.add(
+                keybindings.KeyBinding(
+                    keysymstring,
+                    fullModMask,
+                    modifiers,
+                    self.inputEventHandlers["goNextHandler"]))
+
+            self.functions.append(self.goNext)
+
+        # Set up the "at level" handlers (e.g. to navigate among headings
+        # at the specified level).
+        #
+        previousAtLevel = self.bindings.get("previousAtLevel") or []
+        for i, binding in enumerate(previousAtLevel):
+            level = i + 1
+            handler = self.goPreviousAtLevelFactory(level)
+            handlerName = "goPreviousLevel%dHandler" % level
+            keysymstring, modifiers, description = binding
+
+            self.inputEventHandlers[handlerName] = \
+                input_event.InputEventHandler(handler, description)
+
+            self.keyBindings.add(
+                keybindings.KeyBinding(
+                    keysymstring,
+                    fullModMask,
+                    modifiers,
+                    self.inputEventHandlers[handlerName]))
+
+            self.functions.append(handler)
+
+        nextAtLevel = self.bindings.get("nextAtLevel") or []
+        for i, binding in enumerate(nextAtLevel):
+            level = i + 1
+            handler = self.goNextAtLevelFactory(level)
+            handlerName = "goNextLevel%dHandler" % level
+            keysymstring, modifiers, description = binding
+
+            self.inputEventHandlers[handlerName] = \
+                input_event.InputEventHandler(handler, description)
+
+            self.keyBindings.add(
+                keybindings.KeyBinding(
+                    keysymstring,
+                    fullModMask,
+                    modifiers,
+                    self.inputEventHandlers[handlerName]))
+
+            self.functions.append(handler)
+
+        # Set up the "directional" handlers (e.g. for table cells. Live
+        # region support has a handler to go to the last live region,
+        # so we'll handle that here as well).
+        #
+        directions = {}
+        directions["Left"]  = self.bindings.get("left")
+        directions["Right"] = self.bindings.get("right")
+        directions["Up"]    = self.bindings.get("up")
+        directions["Down"]  = self.bindings.get("down")
+        directions["First"] = self.bindings.get("first")
+        directions["Last"]  = self.bindings.get("last")
+
+        for direction in directions:
+            binding = directions.get(direction)
+            if not binding:
+                continue
+
+            handler = self.goDirectionFactory(direction)
+            handlerName = "go%s" % direction
+            keysymstring, modifiers, description = binding
+
+            self.inputEventHandlers[handlerName] = \
+                input_event.InputEventHandler(handler, description)
+
+            self.keyBindings.add(
+                keybindings.KeyBinding(
+                    keysymstring,
+                    fullModMask,
+                    modifiers,
+                    self.inputEventHandlers[handlerName]))
+
+            self.functions.append(handler)
+
+    def addHandlerAndBinding(self, binding, handlerName, function):
+        """Adds a custom inputEventHandler and keybinding to the object's
+        handlers and bindings.  Right now this is unused, but here in
+        case a creator of a StructNavObject had some other desired
+        functionality in mind.
+
+        Arguments:
+        - binding: [keysymstring, modifiers, description]
+        - handlerName: a string uniquely identifying the handler
+        - function: the function associated with the binding
+        """
+
+        [keysymstring, modifiers, description] = binding
+        handler = input_event.InputEventHandler(function, description)
+        keyBinding = keybindings.KeyBinding(
+                         keysymstring,
+                         fullModMask,
+                         modifiers,
+                         handler)
+
+        self.inputEventHandlers[handlerName] = handler
+        self.sn.inputEventHandlers[handlerName] = handler
+
+        self.functions.append(function)
+        self.sn.functions.append(function)
+
+        self.keyBindings.add(keyBinding)
+        self.sn.keyBindings.add(keyBinding)
+
+    def goPrevious(self, script, inputEvent):
+        """Go to the previous object."""
+        self.sn.goObject(self, False)
+
+    def goNext(self, script, inputEvent):
+        """Go to the next object."""
+        self.sn.goObject(self, True)
+
+    def goPreviousAtLevelFactory(self, level):
+        """Generates a goPrevious method for the specified level. Right
+        now, this is just for headings, but it may have applicability
+        for other objects such as list items (i.e. for level-based
+        navigation in an outline or other multi-tiered list.
+        """
+
+        def goPreviousAtLevel(script, inputEvent):
+            self.sn.goObject(self, False, arg=level)
+        return goPreviousAtLevel
+
+    def goNextAtLevelFactory(self, level):
+        """Generates a goNext method for the specified level. Right
+        now, this is just for headings, but it may have applicability
+        for other objects such as list items (i.e. for level-based
+        navigation in an outline or other multi-tiered list.
+        """
+
+        def goNextAtLevel(script, inputEvent):
+            self.sn.goObject(self, True, arg=level)
+        return goNextAtLevel
+
+    def goDirectionFactory(self, direction):
+        """Generates the methods for navigation in a particular direction
+        (i.e. left, right, up, down, first, last).  Right now, this is
+        primarily for table cells, but it may have applicability for other
+        objects.  For example, when navigating in an outline, one might
+        want the ability to navigate to the next item at a given level,
+        but then work his/her way up/down in the hierarchy.
+        """
+
+        def goCell(script, inputEvent):
+            thisCell = self.sn.getCellForObj(self.sn.getCurrentObject())
+            currentCoords = self.sn.getCellCoordinates(thisCell)
+            if direction == "Left":
+                desiredCoords = [currentCoords[0], currentCoords[1] - 1]
+            elif direction == "Right":
+                desiredCoords = [currentCoords[0], currentCoords[1] + 1]
+            elif direction == "Up":
+                desiredCoords = [currentCoords[0] - 1, currentCoords[1]]
+            elif direction == "Down":
+                desiredCoords = [currentCoords[0] + 1, currentCoords[1]]
+            elif direction == "First":
+                desiredCoords = [0, 0]
+            else:
+                desiredCoords = [-1, -1]
+                table = self.sn.getTableForCell(thisCell)
+                if table:
+                    iTable = table.queryTable()
+                    lastRow = iTable.nRows - 1
+                    lastCol = iTable.nColumns - 1
+                    desiredCoords = [lastRow, lastCol]
+            self.sn.goCell(self, thisCell, currentCoords, desiredCoords)
+
+        def goLastLiveRegion(script, inputEvent):
+            """Go to the last liveRegion."""
+            if settings.inferLiveRegions:
+                script.liveMngr.goLastLiveRegion()
+            else:
+                # Translators: this announces to the user that live region
+                # support has been turned off.
+                #
+                speech.speak(_("Live region support is off"))
+
+        if self.objType == StructuralNavigation.TABLE_CELL:
+            return goCell
+        elif self.objType == StructuralNavigation.LIVE_REGION \
+             and direction == "Last":
+            return goLastLiveRegion
+
+#############################################################################
+#                                                                           #
+# StructuralNavigation                                                      #
+#                                                                           #
+#############################################################################
+
+class StructuralNavigation:
+
+    # These names will be passed along to the structNavCreatorFactory
+    # as the name of the StructNav object.
+    #
+    ANCHOR          = "anchor"
+    BLOCKQUOTE      = "blockquote"
+    BUTTON          = "button"
+    CHECK_BOX       = "checkBox"
+    CHUNK           = "chunk"
+    COMBO_BOX       = "comboBox"
+    ENTRY           = "entry"
+    FORM_FIELD      = "formField"
+    HEADING         = "heading"
+    LANDMARK        = "landmark"
+    LIST            = "list"        # Bulleted/numbered lists
+    LIST_ITEM       = "listItem"    # Bulleted/numbered list items
+    LIVE_REGION     = "liveRegion"
+    RADIO_BUTTON    = "radioButton"
+    TABLE           = "table"
+    TABLE_CELL      = "tableCell"
+    UNVISITED_LINK  = "unvisitedLink"
+    VISITED_LINK    = "visitedLink"
+
+    FORM_OBJECTS = [BUTTON, CHECK_BOX, COMBO_BOX, ENTRY, FORM_FIELD,
+                    RADIO_BUTTON]
+
+    HIERARCHICAL_OBJECTS = [HEADING]
+
+    LINK_OBJECTS = [ANCHOR, UNVISITED_LINK, VISITED_LINK]
+
+    objectTypes = [ANCHOR, BLOCKQUOTE, BUTTON, CHECK_BOX, CHUNK,
+                   COMBO_BOX, ENTRY, FORM_FIELD, LANDMARK, LIST,
+                   LIST_ITEM, LIVE_REGION, RADIO_BUTTON,
+                   TABLE, TABLE_CELL, UNVISITED_LINK, VISITED_LINK]
+
+    # Whether or not to attempt to use Collections.  There's no point
+    # in bothering if we know that the collection interface has not
+    # been implemented in a given app (e.g. StarOffice/OOo) so this
+    # variable
+    #
+    collectionEnabled = settings.useCollection
+
+    # TODO: The next four items are settings for Firefox that apply
+    # to (and are used in) structural navigation.  Perhaps they
+    # should become Orca wide settings which could then be customized
+    # on an app by app basis by the users??? Also, right now these
+    # items are stomping on the FF settings, so that needs to be
+    # addressed regardless.
+
+    # Whether or not to speak the cell's coordinates when navigating
+    # from cell to cell in a table.
+    #
+    speakCellCoordinates = True
+
+    # Whether or not to speak the number of cells spanned by a cell
+    # that occupies more than one row or column of a table.
+    #
+    speakCellSpan = True
+
+    # Whether or not to announce the header that applies to the current
+    # when navigating from cell to cell in a table.
+    #
+    speakCellHeaders = True
+
+    # Whether blank cells should be skipped when navigating in a table
+    # using table navigation commands.
+    #
+    skipBlankCells = False
+
+    # The modifier keys which should be paired with the arrow keys,
+    # Home, and End for the purpose of navigation within a table.
+    #
+    tableNavModifiers = shiftAltModMask
+
+    # Roles which are recognized as being a form field. Note that this
+    # is for the purpose of match rules and predicates and refers to
+    # AT-SPI roles. FORM_OBJECTS, on the other hand, refers to the
+    # StructNavObjects which should be treated as form fields.
+    #
+    FORM_ROLES = [pyatspi.ROLE_CHECK_BOX,
+                  pyatspi.ROLE_RADIO_BUTTON,
+                  pyatspi.ROLE_COMBO_BOX,
+                  pyatspi.ROLE_LIST,
+                  pyatspi.ROLE_ENTRY,
+                  pyatspi.ROLE_PASSWORD_TEXT,
+                  pyatspi.ROLE_PUSH_BUTTON,
+                  pyatspi.ROLE_SPIN_BUTTON,
+                  pyatspi.ROLE_TEXT]
+
+    # Tags associated with ARIA landmarks
+    #
+    ARIA_LANDMARKS = ["banner", "contentinfo", "definition", "main",
+                      "navigation", "note", "search", "secondary",
+                      "seealso"]
+
+    # The minimum size in characters to be considered a "large object"
+    # or "chunk."
+    #
+    largeObjectTextLength = 75
+
+    # Roles which are recognized as being potential "large objects"
+    # or "chunks."
+    #
+    OBJECT_ROLES = [pyatspi.ROLE_HEADING,
+                    pyatspi.ROLE_PARAGRAPH,
+                    pyatspi.ROLE_TABLE,
+                    pyatspi.ROLE_TABLE_CELL,
+                    pyatspi.ROLE_TEXT,
+                    pyatspi.ROLE_SECTION,
+                    pyatspi.ROLE_DOCUMENT_FRAME]
+
+    def __init__(self, script, enabledTypes, enabled=False):
+        self._script = script
+        self.enabled = enabled
+
+        # Create all of the StructNav objects in which the script is
+        # interested.
+        #
+        self.enabledObjects = {}
+        for objType in enabledTypes:
+            snObjCreator = self.structNavCreatorFactory(objType)
+            self.enabledObjects[objType] = snObjCreator(self)
+
+        self.functions = []
+        self.inputEventHandlers = {}
+        self.setupInputEventHandlers()
+        self.keyBindings = self.getKeyBindings()
+
+        # When navigating in a non-uniform table, one can move to a
+        # cell which spans multiple rows and/or columns.  When moving
+        # beyond that cell, into a cell that does NOT span multiple
+        # rows/columns, we want to be sure we land in the right place.
+        # Therefore, we'll store the coordinates from "our perspective."
+        #
+        self.lastTableCell = [-1, -1]
+
+    def structNavCreatorFactory(self, name):
+        """This convenience method generates a method for creating a
+        StructNavObject with the specified name and associated
+        characterists. (See the "Objects" section of code near the
+        end of this class. Creators of StructNavObjects can still
+        do things the old fashioned way should they so choose, by
+        creating the instance and then adding it via addObject().
+
+        Arguments:
+        - name: the name/objType associated with this object.
+        """
+
+        def structNavCreator(self):
+            # We're going to assume bindings.  After all, a structural
+            # navigation object is by defintion an object which one can
+            # navigate to using the associated keybindings. For similar
+            # reasons we'll also assume a predicate and a presentation
+            # method.  (See the Objects section for examples of each.)
+            #
+            bindings = eval("self._%sBindings()" % name)
+            predicate = eval("self._%sPredicate" % name)
+            presentation = eval("self._%sPresentation" % name)
+
+            # We won't make this assumption for match criteria because
+            # the collection interface might not be implemented (e.g.
+            # StarOffice/OpenOffice) and/or its use might not be possible
+            # or practical for a given StructNavObject (e.g. matching by
+            # text attributes, spatial navigation within tables).
+            #
+            try:
+                criteria = eval("self._%sCriteria" % name)
+            except:
+                criteria = None
+
+            return StructNavObject(self, name, bindings, predicate,
+                                   criteria, presentation)
+        return structNavCreator
+
+    def addObject(self, objType, snObj):
+        """Adds the StructNavObject snObj to the dictionary of enabled
+        objects."""
+
+        self.enabledObjects[objType] = snObj
+
+    def setupInputEventHandlers(self):
+        """Defines InputEventHandler fields for a script."""
+
+        if not len(self.enabledObjects):
+            return
+
+        self.inputEventHandlers["toggleStructuralNavigationHandler"] = \
+            input_event.InputEventHandler(
+                self.toggleStructuralNavigation,
+                # Translators: the structural navigation keys are designed
+                # to move the caret around the document content by object
+                # type. Thus H moves you to the next heading, Shift H to
+                # the previous heading, T to the next table, and so on.
+                # This feature needs to be toggle-able so that it does not
+                # interfere with normal writing functions.
+                #
+                _("Toggles structural navigation keys."))
+
+        for snObj in self.enabledObjects.values():
+            self.inputEventHandlers.update(snObj.inputEventHandlers)
+            self.functions.extend(snObj.functions)
+
+    def getKeyBindings(self):
+        """Defines the structural navigationkey bindings for a script.
+
+        Returns an instance of keybindings.KeyBindings.
+        """
+
+        keyBindings = keybindings.KeyBindings()
+
+        if not len(self.enabledObjects):
+            return keyBindings
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "z",
+                fullModMask,
+                orcaModMask,
+                self.inputEventHandlers["toggleStructuralNavigationHandler"]))
+
+        for snObj in self.enabledObjects.values():
+            for keybinding in snObj.keyBindings.keyBindings:
+                keyBindings.add(keybinding)
+
+        return keyBindings
+
+    #########################################################################
+    #                                                                       #
+    # Input Event Handler Methods                                           #
+    #                                                                       #
+    #########################################################################
+
+    def toggleStructuralNavigation(self, script, inputEvent):
+        """Toggles structural navigation keys."""
+
+        self.enabled = not self.enabled
+
+        if self.enabled:
+            # Translators: the structural navigation keys are designed
+            # to move the caret around document content by object type.
+            # Thus H moves you to the next heading, Shift H to the
+            # previous heading, T to the next table, and so on. Some
+            # users prefer to turn this off to use Firefox's search
+            # when typing feature.  This message is sent to both the
+            # braille display and the speech synthesizer when the user
+            # toggles the structural navigation feature of Orca.
+            # It should be a brief informative message.
+            #
+            string = _("Structural navigation keys on.")
+        else:
+            # Translators: the structural navigation keys are designed
+            # to move the caret around document content by object type.
+            # Thus H moves you to the next heading, Shift H to the
+            # previous heading, T to the next table, and so on. Some
+            # users prefer to turn this off to use Firefox's search
+            # when typing feature.  This message is sent to both the
+            # braille display and the speech synthesizer when the user
+            # toggles the structural navigation feature of Orca.
+            # It should be a brief informative message.
+            #
+            string = _("Structural navigation keys off.")
+
+        debug.println(debug.LEVEL_CONFIGURATION, string)
+        speech.speak(string)
+        braille.displayMessage(string)
+
+    #########################################################################
+    #                                                                       #
+    # Methods for Moving to Objects                                         #
+    #                                                                       #
+    #########################################################################
+
+    def goCell(self, snObj, thisCell, currentCoords, desiredCoords):
+        table = self.getTableForCell(thisCell)
+        try:
+            iTable = table.queryTable()
+        except:
+            # Translators: this is for navigating document
+            #  content by moving from table cell to table cell.
+            #
+            speech.speak(_("Not in a table."))
+            return None
+
+        currentRow, currentCol = currentCoords
+        desiredRow, desiredCol = desiredCoords
+        rowDiff = desiredRow - currentRow
+        colDiff = desiredCol - currentCol
+        oldRowHeaders = self._getRowHeaders(thisCell)
+        oldColHeaders = self._getColumnHeaders(thisCell)
+        cell = thisCell
+        while cell:
+            cell = iTable.getAccessibleAt(desiredRow, desiredCol)
+            if not cell:
+                if desiredCol < 0:
+                    # Translators: this is for navigating document
+                    # content by moving from table cell to table cell.
+                    #
+                    speech.speak(_("Beginning of row."))
+                    desiredCol = 0
+                elif desiredCol > iTable.nColumns - 1:
+                    # Translators: this is for navigating document
+                    # content by moving from table cell to table cell.
+                    #
+                    speech.speak(_("End of row."))
+                    desiredCol = iTable.nColumns - 1
+                if desiredRow < 0:
+                    # Translators: this is for navigating document
+                    # content by moving from table cell to table cell.
+                    #
+                    speech.speak(_("Top of column."))
+                    desiredRow = 0
+                elif desiredRow > iTable.nRows - 1:
+                    # Translators: this is for navigating document
+                    # content by moving from table cell to table cell.
+                    #
+                    speech.speak(_("Bottom of column."))
+                    desiredRow = iTable.nRows - 1
+            elif self._script.isSameObject(thisCell, cell) \
+                 or self.skipBlankCells and self._isBlankCell(cell):
+                if colDiff < 0:
+                    desiredCol -= 1
+                elif colDiff > 0:
+                    desiredCol += 1
+                if rowDiff < 0:
+                    desiredRow -= 1
+                elif rowDiff > 0:
+                    desiredRow += 1
+            else:
+                break
+
+        self.lastTableCell = [desiredRow, desiredCol]
+        if cell:
+            arg = [rowDiff, colDiff, oldRowHeaders, oldColHeaders]
+            snObj.present(cell, arg)
+
+    def goObject(self, snObj, next, obj=None, arg=None):
+
+        obj = obj or self.getCurrentObject()
+        success = False
+
+        # Try to find it using Collection first.  But don't do this with form
+        # fields for now.  For some reason it is slower, especially when going
+        # to the previous object. This is probably why Scott didn't implement
+        # it, but maybe there's something we can do to improve the situation
+        # and speed up navigation by form field.
+        #
+        criteria = None
+        if self.collectionEnabled and not snObj.objType in self.FORM_OBJECTS:
+            try:
+                document = self._getDocument()
+                col = document.queryCollection()
+                if snObj.criteria:
+                    criteria = snObj.criteria(col, arg)
+            except:
+                debug.printException(debug.LEVEL_SEVERE)
+
+        if criteria:
+            try:
+                rule = col.createMatchRule(criteria.states.raw(),
+                                           criteria.matchStates,
+                                           criteria.objAttrs,
+                                           criteria.matchObjAttrs,
+                                           criteria.roles,
+                                           criteria.matchRoles,
+                                           criteria.ifaces,
+                                           criteria.matchIfaces,
+                                           criteria.invert)
+
+                if not next:
+                    [obj, wrapped] = self._findPrevByMatchRule(col, rule,
+                                                              True, obj)
+                else:
+                    [obj, wrapped] = self._findNextByMatchRule(col, rule,
+                                                              True, obj)
+                success = True
+                col.freeMatchRule(rule)
+                # TODO: remove this debugging line.  It's helpful for
+                # testing to see which method is actually kicking in.
+                #
+                # print "collection"
+            except NotImplementedError:
+                debug.printException(debug.LEVEL_SEVERE)
+            except:
+                debug.printException(debug.LEVEL_SEVERE)
+                col.freeMatchRule(rule)
+
+        # Do it iteratively when Collection failed or is disabled
+        #
+        if not success:
+            pred = snObj.predicate
+            if not next:
+                [obj, wrapped] = self._findPrevByPredicate(pred, True,
+                                                           obj, arg)
+            else:
+                [obj, wrapped] = self._findNextByPredicate(pred, True,
+                                                           obj, arg)
+            # TODO: remove this debugging line.  It's helpful for
+            # testing to see which method is actually kicking in.
+            #
+            # print "predicate"
+        if wrapped:
+            if not next:
+                # Translators: when the user is attempting to locate a
+                # particular object and the top of the web page has been
+                # reached without that object being found, we "wrap" to
+                # the bottom of the page and continuing looking upwards.
+                # We need to inform the user when this is taking place.
+                #
+                speech.speak(_("Wrapping to bottom."))
+            else:
+                # Translators: when the user is attempting to locate a
+                # particular object and the bottom of the web page has been
+                # reached without that object being found, we "wrap" to the
+                # top of the page and continuing looking downwards. We need
+                # to inform the user when this is taking place.
+                #
+                speech.speak(_("Wrapping to top."))
+
+        snObj.present(obj, arg)
+
+    #########################################################################
+    #                                                                       #
+    # Utility Methods for Finding Objects                                   #
+    #                                                                       #
+    #########################################################################
+
+    def getCurrentObject(self):
+        """Returns the current object.  Normally, the locusOfFocus. But
+        in the case of Gecko, that doesn't always work."""
+
+        return orca_state.locusOfFocus
+
+    def _findPrevByMatchRule(self, col, matchrule, wrap, currentObj):
+        """Finds the caret offset at the beginning of the previous object
+        using the given match rule as a pattern to match or not match.
+
+        Arguments:
+        -col:  the accessible collection interface
+        -matchrule: the collections match rule to use
+        -wrap: if True and the bottom of the document is reached, move
+               to the top and keep looking.
+        -currentObj: the object from which the search should begin
+
+        Returns: [obj, wrapped] where wrapped is a boolean reflecting
+        whether wrapping took place.
+        """
+
+        currentObj = currentObj or self.getCurrentObject()
+        ancestors = []
+        obj = currentObj.parent
+        if obj.getRole() in [pyatspi.ROLE_LIST, pyatspi.ROLE_TABLE]:
+            ancestors.append(obj)
+        else:
+            while obj:
+                ancestors.append(obj)
+                obj = obj.parent
+
+        document = self._getDocument()
+        match, wrapped = None, False
+        rs = col.getMatchesTo(currentObj, matchrule,
+                              col.SORT_ORDER_CANONICAL,
+                              col.TREE_INORDER, True, 1, True)
+        while not match:
+            if len(rs) == 0:
+                if wrapped or not wrap:
+                    break
+                elif wrap:
+                    lastobj = self._findLastObject(document)
+                    # Collection does not do an inclusive search, meaning
+                    # that the start object is not part of the search.  So
+                    # we need to test the lastobj separately using the given
+                    # matchRule.  We don't have this problem for 'Next' because
+                    # the startobj is the doc frame.
+                    #
+                    secondlastobj = self._findPreviousObject(lastobj,
+                                                                    document)
+                    rs = col.getMatchesFrom(secondlastobj, matchrule,
+                                            col.SORT_ORDER_CANONICAL,
+                                            col.TREE_INORDER, 1, True)
+                    wrapped = True
+                    if len(rs) > 0:
+                        match = rs[0]
+                    else:
+                        rs = col.getMatchesTo(lastobj, matchrule,
+                                              col.SORT_ORDER_CANONICAL,
+                                              col.TREE_INORDER, True, 1, True)
+            elif len(rs) > 0:
+                if rs[0] in ancestors:
+                    rs = col.getMatchesTo(rs[0], matchrule,
+                                          col.SORT_ORDER_CANONICAL,
+                                          col.TREE_INORDER, True, 1, True)
+                else:
+                    match = rs[0]
+
+        return [match, wrapped]
+
+    def _findNextByMatchRule(self, col, matchrule, wrap, currentObj=None):
+        """Finds the caret offset at the beginning of the next object
+        using the given match rule as a pattern to match or not match.
+
+        Arguments:
+        -col:  the accessible collection interface
+        -matchrule: the collections match rule to use
+        -wrap: if True and the bottom of the document is reached, move
+               to the top and keep looking.
+        -currentObj: the object from which the search should begin
+
+        Returns: [obj, wrapped] where wrapped is a boolean reflecting
+        whether wrapping took place.
+        """
+
+        currentObj = currentObj or self.getCurrentObject()
+        ancestors = []
+        [currentObj, offset] = self._script.getCaretContext()
+        obj = currentObj.parent
+        while obj:
+            ancestors.append(obj)
+            obj = obj.parent
+
+        rs = col.getMatchesFrom(currentObj,
+                                matchrule,
+                                col.SORT_ORDER_CANONICAL,
+                                col.TREE_INORDER,
+                                1,
+                                True)
+
+        match, wrapped = None, False
+        if len(rs) > 0:
+            match = rs[0]
+        elif wrap:
+            wrapped = True
+            rs = col.getMatchesFrom(self._getDocument(),
+                                    matchrule,
+                                    col.SORT_ORDER_CANONICAL,
+                                    col.TREE_INORDER,
+                                    1,
+                                    True)
+            if len(rs) > 0 and not rs[0] in ancestors:
+                match = rs[0]
+
+        return [match, wrapped]
+
+    def _findPrevByPredicate(self, pred, wrap, currentObj=None, arg=None):
+        """Finds the caret offset at the beginning of the previous object
+        using the given predicate as a pattern to match.
+
+        Arguments:
+        -pred: a python callable that takes an accessible argument and
+               returns true/false based on some match criteria
+        -wrap: if True and the top of the document is reached, move
+               to the bottom and keep looking.
+        -currentObj: the object from which the search should begin
+        -arg:  an additional value to be passed to the predicate
+
+        Returns: [obj, wrapped] where wrapped is a boolean reflecting
+        whether wrapping took place.
+        """
+        currentObj = currentObj or self.getCurrentObject()
+        ancestors = []
+        nestableRoles = [pyatspi.ROLE_LIST, pyatspi.ROLE_TABLE]
+        obj = currentObj.parent
+        while obj:
+            ancestors.append(obj)
+            obj = obj.parent
+
+        document = self._getDocument()
+        obj = self._findPreviousObject(currentObj, document)
+        wrapped = obj is None
+        match = None
+
+        if wrapped:
+            obj = self._findLastObject(document)
+
+        while obj and not match:
+            isNested = (obj != currentObj.parent \
+                        and currentObj.parent.getRole() == obj.getRole() \
+                        and obj.getRole() in nestableRoles)
+            if (not obj in ancestors or isNested) and pred(obj):
+                if wrapped and self._script.isSameObject(currentObj, obj):
+                    break
+                else:
+                    match = obj
+            else:
+                obj = self._findPreviousObject(obj, document)
+                if not obj and wrap and not wrapped:
+                    obj = self._findLastObject(document)
+                    wrapped = True
+
+        return [match, wrapped]
+
+    def _findNextByPredicate(self, pred, wrap, currentObj=None, arg=None):
+        """Finds the caret offset at the beginning of the next object
+        using the given predicate as a pattern to match or not match.
+
+        Arguments:
+        -pred: a python callable that takes an accessible argument and
+               returns true/false based on some match criteria
+        -wrap: if True and the bottom of the document is reached, move
+               to the top and keep looking.
+        -currentObj: the object from which the search should begin
+        -arg:  an additional value to be passed to the predicate
+
+        Returns: [obj, wrapped] where wrapped is a boolean reflecting
+        whether wrapping took place.
+        """
+        currentObj = currentObj or self.getCurrentObject()
+        ancestors = []
+        obj = currentObj.parent
+        while obj:
+            ancestors.append(obj)
+            obj = obj.parent
+
+        document = self._getDocument()
+        obj = self._findNextObject(currentObj, document)
+        wrapped = obj is None
+        match = None
+
+        if wrapped:
+            [obj, offset] = self._getCaretPosition(document)
+
+        while obj and not match:
+            if (not obj in ancestors) and pred(obj, arg):
+                if wrapped and self._script.isSameObject(currentObj, obj):
+                    break
+                else:
+                    match = obj
+            else:
+                obj = self._findNextObject(obj, document)
+                if not obj and wrap and not wrapped:
+                    [obj, offset] = self._getCaretPosition(document)
+                    wrapped = True
+
+        return [match, wrapped]
+
+    def _findPreviousObject(self, obj, stopAncestor):
+        """Finds the object prior to this one, where the tree we're
+        dealing with is a DOM and 'prior' means the previous object
+        in a linear presentation sense.
+
+        Arguments:
+        -obj: the object where to start.
+        -stopAncestor: the ancestor at which the search should stop
+        """
+
+        # NOTE: This method is based on some intial experimentation
+        # with OOo structural navigation.  It might need refining
+        # or fixing and is being overridden by the Gecko method
+        # regardless, so this one can be modified as appropriate.
+        #
+        prevObj = None
+
+        index = obj.getIndexInParent() - 1
+        if index >= 0:
+            prevObj = obj.parent[index]
+            if prevObj.childCount:
+                prevObj = prevObj[prevObj.childCount - 1]
+        elif not self._script.isSameObject(obj.parent, stopAncestor):
+            prevObj = obj.parent
+
+        return prevObj
+
+    def _findNextObject(self, obj, stopAncestor):
+        """Finds the object after to this one, where the tree we're
+        dealing with is a DOM and 'next' means the next object
+        in a linear presentation sense.
+
+        Arguments:
+        -obj: the object where to start.
+        -stopAncestor: the ancestor at which the search should stop
+        """
+
+        # NOTE: This method is based on some intial experimentation
+        # with OOo structural navigation.  It might need refining
+        # or fixing and is being overridden by the Gecko method
+        # regardless, so this one can be modified as appropriate.
+        #
+        nextObj = None
+
+        if obj and obj.childCount:
+            nextObj = obj[0]
+
+        while obj and obj.parent != obj and not nextObj:
+            index = obj.getIndexInParent() + 1
+            if 0 < index < obj.parent.childCount:
+                nextObj = obj.parent[index]
+            elif not self._script.isSameObject(obj.parent, stopAncestor):
+                obj = obj.parent
+            else:
+                break
+
+        return nextObj
+
+    def _findLastObject(self, ancestor):
+        """Returns the last object in ancestor"""
+
+        # NOTE: This method is based on some intial experimentation
+        # with OOo structural navigation.  It might need refining
+        # or fixing and is being overridden by the Gecko method
+        # regardless, so this one can be modified as appropriate.
+        #
+        if not ancestor or not ancestor.childCount:
+            return ancestor
+
+        lastChild = ancestor[ancestor.childCount - 1]
+        while lastChild:
+            lastObj = self._findNextObject(lastChild, ancestor)
+            if lastObj:
+                lastChild = lastObj
+            else:
+                break
+
+        return lastChild
+
+    def _getDocument(self):
+        """Returns the document or other object in which the object of
+        interest is contained.
+        """
+        # This is script-specific and will need to be defined in the
+        # script's custom StructuralNavigation class. But if this
+        # method does nothing, pylint complains.  So... We might as
+        # well take a guess for a generic version to make pylint
+        # happy. :-) In some initial experimentation with OOo, this
+        # method seemed to reliably return the child of the document
+        # view, so it might not be too far off.  It's also being
+        # overridden by Gecko, so one should feel free to modify this
+        # one.
+        #
+        obj = self.getCurrentObject()
+        lastTextObj = obj
+        while obj and obj.getRole() != pyatspi.ROLE_FRAME:
+            try:
+                obj.queryText()
+            except:
+                pass
+            else:
+                lastTextObj = obj
+            obj = obj.parent
+
+        return lastTextObj
+
+    def _isInDocument(self, obj):
+        """Returns True of the object is inside of the document."""
+
+        document = self._getDocument()
+        while obj and obj.parent:
+            if self._script.isSameObject(obj.parent, document):
+                return True
+            else:
+                obj = obj.parent
+
+        return False
+
+    def _isUselessObject(self, obj):
+        """Returns true if the given object is an obj that doesn't
+        have any meaning associated with it. Individual scripts
+        should override this method as needed.  Gecko does. :-)
+        """
+
+        return False
+
+    #########################################################################
+    #                                                                       #
+    # Methods for Presenting Objects                                        #
+    #                                                                       #
+    #########################################################################
+
+    def _getTableCaption(self, obj):
+        """Returns a string which contains the table caption, or
+        None if a caption could not be found."""
+
+        caption = None
+        for child in obj:
+            if child and (child.getRole() == pyatspi.ROLE_CAPTION):
+                caption = child
+
+        try:
+            caption.queryText()
+        except:
+            return None
+        else:
+            return self._script.getDisplayedText(caption)
+
+    def _getTableDescription(self, obj):
+        """Returns a string which describes the table."""
+
+        nonUniformString = ""
+        nonUniform = self._isNonUniformTable(obj)
+        if nonUniform:
+            # Translators: a uniform table is one in which each table
+            # cell occupies one row and one column (i.e. a perfect grid)
+            # In contrast, a non-uniform table is one in which at least
+            # one table cell occupies more than one row and/or column.
+            #
+            nonUniformString = _("Non-uniform") + " "
+
+        table = obj.queryTable()
+        nRows = table.nRows
+        nColumns = table.nColumns
+        # Translators: this represents the number of rows in a table.
+        #
+        rowString = ngettext("Table with %d row",
+                             "Table with %d rows",
+                             nRows) % nRows
+        # Translators: this represents the number of cols in a table.
+        #
+        colString = ngettext("%d column",
+                             "%d columns",
+                             nColumns) % nColumns
+
+        return (nonUniformString + rowString + " " + colString)
+
+    def _isNonUniformTable(self, obj):
+        """Returns True if the obj is a non-uniform table (i.e. a table
+        where at least one cell spans multiple rows and/or columns).
+
+        Arguments:
+        - obj: the table to examine
+        """
+
+        try:
+            table = obj.queryTable()
+        except:
+            pass
+        else:
+            for i in xrange(obj.childCount):
+                [isCell, row, col, rowExtents, colExtents, isSelected] = \
+                                       table.getRowColumnExtentsAtIndex(i)
+                if (rowExtents > 1) or (colExtents > 1):
+                    return True
+
+        return False
+
+    def getCellForObj(self, obj):
+        """Looks for a table cell in the ancestry of obj, if obj is not a
+        table cell.
+
+        Arguments:
+        - obj: the object of interest.
+        """
+
+        if obj and obj.getRole() != pyatspi.ROLE_TABLE_CELL:
+            document = self._getDocument()
+            obj = self._script.getAncestor(obj,
+                                           [pyatspi.ROLE_TABLE_CELL],
+                                           [document.getRole()])
+        return obj
+
+    def getTableForCell(self, obj):
+        """Looks for a table in the ancestry of obj, if obj is not a table.
+
+        Arguments:
+        - obj: the object of interest.
+        """
+
+        if obj and obj.getRole() != pyatspi.ROLE_TABLE:
+            document = self._getDocument()
+            obj = self._script.getAncestor(obj,
+                                           [pyatspi.ROLE_TABLE],
+                                           [document.getRole()])
+        return obj
+
+    def _isBlankCell(self, obj):
+        """Returns True if the table cell is empty or consists of whitespace.
+
+        Arguments:
+        - obj: the table cell to examime
+        """
+
+        text = self._script.getDisplayedText(obj)
+        if text and len(text.strip()) and text != obj.name:
+            return False
+        else:
+            for child in obj:
+                text = self._script.getDisplayedText(child)
+                if text and len(text.strip()) \
+                   or child.getRole() == pyatspi.ROLE_LINK:
+                    return False
+
+        return True
+
+    def _getCellText(self, obj):
+        """Looks at the table cell and tries to get its text."""
+        text = ""
+        if obj and not obj.childCount:
+            text = self._script.getDisplayedText(obj)
+        else:
+            for child in obj:
+                childText = self._script.getDisplayedText(child)
+                text = self._script.appendString(text, childText)
+
+        return text
+
+    def _presentCellHeaders(self, cell, arg=None):
+        if not cell or not arg:
+            return
+
+        rowDiff, colDiff, oldRowHeaders, oldColHeaders = arg
+        if not (oldRowHeaders and oldColHeaders):
+            return
+
+        # We only want to speak the header information that has
+        # changed, and we don't want to speak headers if we're in
+        # a header row/col.
+        #
+        if rowDiff and not self._isInHeaderRow(cell):
+            rowHeaders = self._getRowHeaders(cell)
+            for header in rowHeaders:
+                if not header in oldRowHeaders:
+                    text = self._getCellText(header)
+                    speech.speak(text)
+
+        if colDiff and not self._isInHeaderColumn(cell):
+            colHeaders = self._getColumnHeaders(cell)
+            for header in colHeaders:
+                if not header in oldColHeaders:
+                    text = self._getCellText(header)
+                    speech.speak(text)
+
+    def _getCellSpanInfo(self, obj):
+        """Returns a string reflecting the number of rows and/or columns
+        spanned by a table cell when multiple rows and/or columns are spanned.
+        """
+
+        if not obj or (obj.getRole() != pyatspi.ROLE_TABLE_CELL):
+            return
+
+        [row, col] = self.getCellCoordinates(obj)
+        table = obj.parent.queryTable()
+        rowspan = table.getRowExtentAt(row, col)
+        colspan = table.getColumnExtentAt(row, col)
+        spanString = ""
+        if (colspan > 1) and (rowspan > 1):
+            # Translators: The cell here refers to a cell within a table
+            # within a document.  We need to announce when the cell occupies
+            # or "spans" more than a single row and/or column.
+            #
+            spanString = _("Cell spans %d rows and %d columns") % \
+                          (rowspan, colspan)
+        elif (colspan > 1):
+            # Translators: The cell here refers to a cell within a table
+            # within a document.  We need to announce when the cell occupies
+            # or "spans" more than a single row and/or column.
+            #
+            spanString = _("Cell spans %d columns") % colspan
+        elif (rowspan > 1):
+            # Translators: The cell here refers to a cell within a table
+            # within a document.  We need to announce when the cell occupies
+            # or "spans" more than a single row and/or column.
+            #
+            spanString = _("Cell spans %d rows") % rowspan
+
+        return spanString
+
+    def getCellCoordinates(self, obj):
+        """Returns the [row, col] of a ROLE_TABLE_CELL or [-1, -1]
+        if the coordinates cannot be found.
+        """
+
+        obj = self.getCellForObj(obj)
+        parent = self.getTableForCell(obj)
+        try:
+            table = parent.queryTable()
+        except:
+            pass
+        else:
+            # If we're in a cell that spans multiple rows and/or columns,
+            # thisRow and thisCol will refer to the upper left cell in
+            # the spanned range(s).  We're storing the lastTableCell that
+            # we're aware of in order to facilitate more linear movement.
+            # Therefore, if the lastTableCell and this table cell are the
+            # same cell, we'll go with the stored coordinates.
+            #
+            lastRow, lastCol = self.lastTableCell
+            lastKnownCell = table.getAccessibleAt(lastRow, lastCol)
+            if self._script.isSameObject(lastKnownCell, obj):
+                return [lastRow, lastCol]
+            else:
+                index = self._script.getCellIndex(obj)
+                thisRow = table.getRowAtIndex(index)
+                thisCol = table.getColumnAtIndex(index)
+                return [thisRow, thisCol]
+
+        return [-1, -1]
+
+    def _getRowHeaders(self, obj):
+        """Returns a list of table cells that serve as a row header for
+        the specified TABLE_CELL.
+        """
+
+        rowHeaders = []
+        if not obj:
+            return rowHeaders
+
+        try:
+            table = obj.parent.queryTable()
+        except:
+            pass
+        else:
+            [row, col] = self.getCellCoordinates(obj)
+            # Theoretically, we should be able to quickly get the text
+            # of a {row, column}Header via get{Row,Column}Description().
+            # Mozilla doesn't expose the information that way, however.
+            # get{Row, Column}Header seems to work sometimes.
+            #
+            header = table.getRowHeader(row)
+            if header:
+                rowHeaders.append(header)
+
+            # Headers that are strictly marked up with <th> do not seem
+            # to be exposed through get{Row, Column}Header.
+            #
+            else:
+                # If our cell spans multiple rows, we want to get all of
+                # the headers that apply.
+                #
+                rowspan = table.getRowExtentAt(row, col)
+                for r in range(row, row+rowspan):
+                    # We could have multiple headers for a given row, one
+                    # header per column.  Presumably all of the headers are
+                    # prior to our present location.
+                    #
+                    for c in range(0, col):
+                        cell = table.getAccessibleAt(r, c)
+                        if self._isHeader(cell) and not cell in rowHeaders:
+                            rowHeaders.append(cell)
+
+        return rowHeaders
+
+    def _getColumnHeaders(self, obj):
+        """Returns a list of table cells that serve as a column header for
+        the specified TABLE_CELL.
+        """
+
+        columnHeaders = []
+        if not obj:
+            return columnHeaders
+
+        try:
+            table = obj.parent.queryTable()
+        except:
+            pass
+        else:
+            [row, col] = self.getCellCoordinates(obj)
+            # Theoretically, we should be able to quickly get the text
+            # of a {row, column}Header via get{Row,Column}Description().
+            # Mozilla doesn't expose the information that way, however.
+            # get{Row, Column}Header seems to work sometimes.
+            #
+            header = table.getColumnHeader(col)
+            if header:
+                columnHeaders.append(header)
+
+            # Headers that are strictly marked up with <th> do not seem
+            # to be exposed through get{Row, Column}Header.
+            #
+            else:
+                # If our cell spans multiple columns, we want to get all of
+                # the headers that apply.
+                #
+                colspan = table.getColumnExtentAt(row, col)
+                for c in range(col, col+colspan):
+                    # We could have multiple headers for a given column, one
+                    # header per row.  Presumably all of the headers are
+                    # prior to our present location.
+                    #
+                    for r in range(0, row):
+                        cell = table.getAccessibleAt(r, c)
+                        if self._isHeader(cell) and not cell in columnHeaders:
+                            columnHeaders.append(cell)
+
+        return columnHeaders
+
+    def _isInHeaderRow(self, obj):
+        """Returns True if all of the cells in the same row as this cell are
+        headers.
+
+        Arguments:
+        - obj: the table cell whose row is to be examined
+        """
+
+        if obj and obj.getRole() == pyatspi.ROLE_TABLE_CELL:
+            table = obj.parent.queryTable()
+            index = self._script.getCellIndex(obj)
+            row = table.getRowAtIndex(index)
+            for col in xrange(table.nColumns):
+                cell = table.getAccessibleAt(row, col)
+                if not self._isHeader(cell):
+                    return False
+
+        return True
+
+    def _isInHeaderColumn(self, obj):
+        """Returns True if all of the cells in the same column as this cell
+        are headers.
+
+        Arguments:
+        - obj: the table cell whose column is to be examined
+        """
+
+        if obj and obj.getRole() == pyatspi.ROLE_TABLE_CELL:
+            table = obj.parent.queryTable()
+            index = self._script.getCellIndex(obj)
+            col = table.getColumnAtIndex(index)
+            for row in xrange(table.nRows):
+                cell = table.getAccessibleAt(row, col)
+                if not self._isHeader(cell):
+                    return False
+
+        return True
+
+    def _isHeader(self, obj):
+        """Returns True if the table cell is a header"""
+
+        if not obj:
+            return False
+
+        elif obj.getRole() in [pyatspi.ROLE_TABLE_COLUMN_HEADER,
+                               pyatspi.ROLE_TABLE_ROW_HEADER]:
+            return True
+
+        else:
+            attributes = obj.getAttributes()
+            if attributes:
+                for attribute in attributes:
+                    if attribute == "tag:TH":
+                        return True
+
+    def _getHeadingLevel(self, obj):
+        """Determines the heading level of the given object.  A value
+        of 0 means there is no heading level.
+        """
+
+        level = 0
+
+        if obj is None:
+            return level
+
+        if obj.getRole() == pyatspi.ROLE_HEADING:
+            attributes = obj.getAttributes()
+            if attributes is None:
+                return level
+            for attribute in attributes:
+                if attribute.startswith("level:"):
+                    level = int(attribute.split(":")[1])
+                    break
+
+        return level
+
+    def _getCaretPosition(self, obj):
+        """Returns the [obj, characterOffset] where the caret should be
+        positioned.
+        """
+
+        return [obj, 0]
+
+    def _setCaretPosition(self, obj, characterOffset):
+        """Sets the caret at the specified offset within obj."""
+
+        try:
+            text = obj.queryText()
+            text.setCaretOffset(characterOffset)
+        except NotImplementedError:
+            try:
+                obj.queryComponent().grabFocus()
+            except:
+                debug.printException(debug.LEVEL_SEVERE)
+        except:
+            debug.printException(debug.LEVEL_SEVERE)
+
+        orca.setLocusOfFocus(None, obj, False)
+
+    def _presentLine(self, obj, offset):
+        """Presents the first line of the object to the user."""
+        self._script.updateBraille(obj)
+        self._script.sayLine(obj)
+
+    def _presentObject(self, obj, offset):
+        """Presents the entire object to the user."""
+        self._script.updateBraille(obj)
+        utterances = []
+        strings = self._script.speechGenerator.getSpeech(obj, False)
+        for string in strings:
+            voice = self._getVoice(obj, string)
+            speech.speak(string, voice)
+
+    def _getVoice(self, obj, string):
+        """Returns the voice to speak anything for the given obj."""
+        voices = self._script.voices
+
+        if obj.getRole() == pyatspi.ROLE_LINK:
+            voice = voices[settings.HYPERLINK_VOICE]
+        elif string and string.isupper() and string.strip().isalpha():
+            voice = voices[settings.UPPERCASE_VOICE]
+        else:
+            voice = voices[settings.DEFAULT_VOICE]
+
+        return voice
+
+    #########################################################################
+    #                                                                       #
+    # Objects                                                               #
+    #                                                                       #
+    #########################################################################
+
+    # All structural navigation objects have the following essential
+    # characteristics:
+    #
+    # 1. Keybindings for goPrevious, goNext, and other such methods
+    # 2. A means of identification (at least a predicate and possibly
+    #    also criteria for generating a collection match rule)
+    # 3. A definition of how the object should be presented (both
+    #    when another instance of that object is found as well as
+    #    when it is not)
+    #
+    # Convenience methods have been put into place whereby one can
+    # create an object (FOO = "foo"), and then provide the following
+    # methods: _fooBindings(), _fooPredicate(), _fooCriteria(), and
+    # _fooPresentation().  With these in place, and with the object
+    # FOO included among the StructuralNavigation.enabledTypes for
+    # the script, the structural navigation object should be created
+    # and set up automagically. At least that is the idea. :-) This
+    # hopefully will also enable easy re-definition of existing
+    # objects on a script-by-script basis.  For instance, in the
+    # StarOffice script, overriding the _blockquotePredicate should
+    # be all that is needed to implement navigation by blockquote
+    # in OOo Writer documents.
+    #
+
+    ########################
+    #                      #
+    # Anchors              #
+    #                      #
+    ########################
+
+    def _anchorBindings(self):
+        # NOTE: This doesn't handle the case where the anchor is not an
+        # old-school <a name/id="foo"></a> anchor. For instance on the
+        # GNOME wiki, an "anchor" is actually an id applied to some other
+        # tag (e.g. <h2 id="foo">My Heading</h2>.  We'll have to be a
+        # bit more clever for those.  With the old-school anchors, this
+        # seems to work nicely and provides the user with a way to jump
+        # among defined areas without having to find a Table of Contents
+        # group of links (assuming such a thing is even present on the
+        # page).
+
+        bindings = {}
+        # Translators: this is for navigating among anchors in a document.
+        # An anchor is a named spot that one can jump to.
+        #
+        prevDesc = _("Goes to previous anchor.")
+        bindings["previous"] = ["", noModMask, prevDesc]
+        # Translators: this is for navigating among anchors in a document.
+        # An anchor is a named spot that one can jump to.
+        #
+        nextDesc = _("Goes to next anchor.")
+        bindings["next"] = ["", noModMask, nextDesc]
+        return bindings
+
+    def _anchorCriteria(self, col, arg=None):
+        role = [pyatspi.ROLE_LINK]
+        state = [pyatspi.STATE_FOCUSABLE, pyatspi.STATE_VISIBLE]
+        stateMatch = col.MATCH_NONE
+        return MatchCriteria(col,
+                             states=state,
+                             matchStates=stateMatch,
+                             roles=role)
+
+    def _anchorPredicate(self, obj, arg=None):
+        isMatch = False
+        if obj and obj.getRole() == pyatspi.ROLE_LINK:
+            state = obj.getState()
+            isMatch = not (state.contains(pyatspi.STATE_VISIBLE) \
+                           or state.contains(pyatspi.STATE_FOCUSABLE))
+        return isMatch
+
+    def _anchorPresentation(self, obj, arg=None):
+        if obj:
+            [obj, characterOffset] = self._getCaretPosition(obj)
+            self._setCaretPosition(obj, characterOffset)
+            self._presentObject(obj, characterOffset)
+        else:
+            # Translators: this is for navigating document content by
+            # moving from anchor to anchor. (An anchor is a named spot
+            # that one can jump to. This stirng is what orca will say
+            # if there are no more anchors found.
+            #
+            speech.speak(_("No more anchors."))
+
+    ########################
+    #                      #
+    # Blockquotes          #
+    #                      #
+    ########################
+
+    def _blockquoteBindings(self):
+        bindings = {}
+        # Translators: this is for navigating among blockquotes in a
+        # document.
+        #
+        prevDesc = _("Goes to previous blockquote.")
+        bindings["previous"] = ["q", shiftModMask, prevDesc]
+        # Translators: this is for navigating among blockquotes in a
+        # document.
+        #
+        nextDesc = _("Goes to next blockquote.")
+        bindings["next"] = ["q", noModMask, nextDesc]
+        return bindings
+
+    def _blockquoteCriteria(self, col, arg=None):
+        attrs = ['tag:BLOCKQUOTE']
+        return MatchCriteria(col, objAttrs=attrs)
+
+    def _blockquotePredicate(self, obj, arg=None):
+        if not obj:
+            return False
+
+        attributes = obj.getAttributes()
+        if attributes:
+            for attribute in attributes:
+                if attribute == "tag:BLOCKQUOTE":
+                    return True
+
+        return False
+
+    def _blockquotePresentation(self, obj, arg=None):
+        if obj:
+            [obj, characterOffset] = self._getCaretPosition(obj)
+            self._setCaretPosition(obj, characterOffset)
+            # TODO: We currently present the line, so that's kept here.
+            # But we should probably present the object, which would
+            # be consistent with the change made recently for headings.
+            #
+            self._presentLine(obj, characterOffset)
+        else:
+            # Translators: this is for navigating document content by
+            # moving from blockquote to blockquote. This string is what
+            # Orca will say if there are no more blockquotes found.
+            #
+            speech.speak(_("No more blockquotes."))
+
+    ########################
+    #                      #
+    # Buttons              #
+    #                      #
+    ########################
+
+    def _buttonBindings(self):
+        bindings = {}
+        # Translators: this is for navigating among buttons in a form
+        # within a document.
+        #
+        prevDesc = _("Goes to previous button.")
+        bindings["previous"] = ["b", shiftModMask, prevDesc]
+        # Translators: this is for navigating among buttons in a form
+        # within a document.
+        #
+        nextDesc = _("Goes to next button.")
+        bindings["next"] = ["b", noModMask, nextDesc]
+        return bindings
+
+    def _buttonCriteria(self, col, arg=None):
+        role = [pyatspi.ROLE_PUSH_BUTTON]
+        state = [pyatspi.STATE_FOCUSABLE, pyatspi.STATE_SENSITIVE]
+        stateMatch = col.MATCH_ALL
+        return MatchCriteria(col,
+                             states=state,
+                             matchStates=stateMatch,
+                             roles=role)
+
+    def _buttonPredicate(self, obj, arg=None):
+        isMatch = False
+        if obj and obj.getRole() == pyatspi.ROLE_PUSH_BUTTON:
+            state = obj.getState()
+            isMatch = state.contains(pyatspi.STATE_FOCUSABLE) \
+                  and state.contains(pyatspi.STATE_SENSITIVE)
+
+        return isMatch
+
+    def _buttonPresentation(self, obj, arg=None):
+        if obj:
+            obj.queryComponent().grabFocus()
+        else:
+            # Translators: this is for navigating in document content
+            # by moving from push button to push button in a form. This
+            # string is what Orca will say if there are no more buttons
+            # found.
+            #
+            speech.speak(_("No more buttons."))
+
+    ########################
+    #                      #
+    # Check boxes          #
+    #                      #
+    ########################
+
+    def _checkBoxBindings(self):
+        bindings = {}
+        # Translators: this is for navigating among check boxes in a form
+        # within a document.
+        #
+        prevDesc = _("Goes to previous check box.")
+        bindings["previous"] = ["x", shiftModMask, prevDesc]
+        # Translators: this is for navigating among check boxes in a form
+        # within a document.
+        #
+        nextDesc = _("Goes to next check box.")
+        bindings["next"] = ["x", noModMask, nextDesc]
+        return bindings
+
+    def _checkBoxCriteria(self, col, arg=None):
+        role = [pyatspi.ROLE_CHECK_BOX]
+        state = [pyatspi.STATE_FOCUSABLE, pyatspi.STATE_SENSITIVE]
+        stateMatch = col.MATCH_ALL
+        return MatchCriteria(col,
+                             states=state,
+                             matchStates=stateMatch,
+                             roles=role)
+
+    def _checkBoxPredicate(self, obj, arg=None):
+        isMatch = False
+        if obj and obj.getRole() == pyatspi.ROLE_CHECK_BOX:
+            state = obj.getState()
+            isMatch = state.contains(pyatspi.STATE_FOCUSABLE) \
+                  and state.contains(pyatspi.STATE_SENSITIVE)
+
+        return isMatch
+
+    def _checkBoxPresentation(self, obj, arg=None):
+        if obj:
+            obj.queryComponent().grabFocus()
+        else:
+            # Translators: this is for navigating in document content
+            # by moving from checkbox to checkbox in a form. This
+            # string is what Orca will say if there are no more check
+            # boxes found.
+            #
+            speech.speak(_("No more check boxes."))
+
+    ########################
+    #                      #
+    # Chunks/Large Objects #
+    #                      #
+    ########################
+
+    def _chunkBindings(self):
+        bindings = {}
+        # Translators: this is for navigating a document in a
+        # structural manner, where a 'large object' is a logical
+        # chunk of text, such as a paragraph, a list, a table, etc.
+        #
+        prevDesc = _("Goes to previous large object.")
+        bindings["previous"] = ["o", shiftModMask, prevDesc]
+        # Translators: this is for navigating a document in a
+        # structural manner, where a 'large object' is a logical
+        # chunk of text, such as a paragraph, a list, a table, etc.
+        #
+        nextDesc = _("Goes to next large object.")
+        bindings["next"] = ["o", noModMask, nextDesc]
+        # I don't think it makes sense to add support for a list
+        # of chunks.  But one could always change that here.
+        #
+        return bindings
+
+    def _chunkPredicate(self, obj, arg=None):
+        isMatch = False
+
+        if obj and obj.getRole() in self.OBJECT_ROLES:
+            try:
+                text = obj.queryText()
+                characterCount = text.characterCount
+            except:
+                characterCount = 0
+
+            if characterCount > self.largeObjectTextLength \
+               and not self._isUselessObject(obj):
+                isMatch = True
+
+        return isMatch
+
+    def _chunkPresentation(self, obj, arg=None):
+        if obj:
+            [obj, characterOffset] = self._getCaretPosition(obj)
+            self._setCaretPosition(obj, characterOffset)
+            self._presentObject(obj, characterOffset)
+        else:
+            # Translators: this is for navigating document content by
+            # moving from 'large object' to 'large object'. A 'large
+            # object' is a logical chunk of text, such as a paragraph,
+            # a list, a table, etc. This string is what Orca will say
+            # if there are no more large objects found.
+            #
+            speech.speak(_("No more large objects."))
+
+    ########################
+    #                      #
+    # Combo Boxes          #
+    #                      #
+    ########################
+
+    def _comboBoxBindings(self):
+        bindings = {}
+        # Translators: this is for navigating among combo boxes in a form
+        # within a document.
+        #
+        prevDesc = _("Goes to previous combo box.")
+        bindings["previous"] = ["c", shiftModMask, prevDesc]
+        # Translators: this is for navigating among combo boxes in a form
+        # within a document.
+        #
+        nextDesc = _("Goes to next combo box.")
+        bindings["next"] = ["c", noModMask, nextDesc]
+        return bindings
+
+    def _comboBoxCriteria(self, col, arg=None):
+        role = [pyatspi.ROLE_COMBO_BOX]
+        state = [pyatspi.STATE_FOCUSABLE, pyatspi.STATE_SENSITIVE]
+        stateMatch = col.MATCH_ALL
+        return MatchCriteria(col,
+                             states=state,
+                             matchStates=stateMatch,
+                             roles=role)
+
+    def _comboBoxPredicate(self, obj, arg=None):
+        isMatch = False
+        if obj and obj.getRole() == pyatspi.ROLE_COMBO_BOX:
+            state = obj.getState()
+            isMatch = state.contains(pyatspi.STATE_FOCUSABLE) \
+                  and state.contains(pyatspi.STATE_SENSITIVE)
+
+        return isMatch
+
+    def _comboBoxPresentation(self, obj, arg=None):
+        if obj:
+            obj.queryComponent().grabFocus()
+        else:
+            # Translators: this is for navigating in document content
+            # by moving from combo box to combo box in a form. This
+            # string is what Orca will say if there are no more combo
+            # boxes found.
+            #
+            speech.speak(_("No more combo boxes."))
+
+    ########################
+    #                      #
+    # Entries              #
+    #                      #
+    ########################
+
+    def _entryBindings(self):
+        bindings = {}
+        # Translators: this is for navigating among text entries in a form
+        # within a document.
+        #
+        prevDesc = _("Goes to previous entry.")
+        bindings["previous"] = ["e", shiftModMask, prevDesc]
+        # Translators: this is for navigating among text entries
+        # in a form.
+        #
+        nextDesc = _("Goes to next entry.")
+        bindings["next"] = ["e", noModMask, nextDesc]
+        return bindings
+
+    def _entryCriteria(self, col, arg=None):
+        role = [pyatspi.ROLE_ENTRY,
+                pyatspi.ROLE_PASSWORD_TEXT,
+                pyatspi.ROLE_TEXT]
+        roleMatch = col.MATCH_ANY
+        state = [pyatspi.STATE_FOCUSABLE,
+                 pyatspi.STATE_SENSITIVE,
+                 pyatspi.STATE_EDITABLE]
+        stateMatch = col.MATCH_ALL
+        return MatchCriteria(col,
+                             states=state,
+                             matchStates=stateMatch,
+                             roles=role,
+                             matchRoles=roleMatch)
+
+    def _entryPredicate(self, obj, arg=None):
+        isMatch = False
+        if obj and obj.getRole() in [pyatspi.ROLE_ENTRY,
+                                     pyatspi.ROLE_PASSWORD_TEXT,
+                                     pyatspi.ROLE_TEXT]:
+            state = obj.getState()
+            isMatch = state.contains(pyatspi.STATE_FOCUSABLE) \
+                  and state.contains(pyatspi.STATE_SENSITIVE) \
+                  and state.contains(pyatspi.STATE_EDITABLE)
+
+        return isMatch
+
+    def _entryPresentation(self, obj, arg=None):
+        if obj:
+            obj.queryComponent().grabFocus()
+        else:
+            # Translators: this is for navigating in document content
+            # by moving from text entry to text entry in a form. This
+            # string is what Orca will say if there are no more entries
+            # found.
+            #
+            speech.speak(_("No more entries."))
+
+    ########################
+    #                      #
+    # Form Fields          #
+    #                      #
+    ########################
+
+    def _formFieldBindings(self):
+        bindings = {}
+        # Translators: this is for navigating among fields in a form within
+        # a document.
+        #
+        prevDesc = _("Goes to previous form field.")
+        bindings["previous"] = ["Tab", orcaShiftModMask, prevDesc]
+        # Translators: this is for navigating among fields in a form within
+        # a document.
+        #
+        nextDesc = _("Goes to next form field.")
+        bindings["next"] = ["Tab", orcaModMask, nextDesc]
+        return bindings
+
+    def _formFieldCriteria(self, col, arg=None):
+        role = self.FORM_ROLES
+        roleMatch = col.MATCH_ANY
+        state = [pyatspi.STATE_FOCUSABLE, pyatspi.STATE_SENSITIVE]
+        stateMatch = col.MATCH_ALL
+        return MatchCriteria(col,
+                             states=state,
+                             matchStates=stateMatch,
+                             roles=role,
+                             matchRoles=roleMatch)
+
+    def _formFieldPredicate(self, obj, arg=None):
+        isMatch = False
+        if obj and obj.getRole() in self.FORM_ROLES:
+            state = obj.getState()
+            isMatch = state.contains(pyatspi.STATE_FOCUSABLE) \
+                  and state.contains(pyatspi.STATE_SENSITIVE)
+
+        return isMatch
+
+    def _formFieldPresentation(self, obj, arg=None):
+        if obj:
+            if obj.getRole() in [pyatspi.ROLE_LIST, pyatspi.ROLE_COMBO_BOX]:
+                obj.queryComponent().grabFocus()
+            else:
+                # TODO: I think we should just grab focus on the object
+                # regardless of the object type.  But that's not what we
+                # do now, and it causes an extra newline character to show
+                # up in the regression test output for entries, so for the
+                # purpose of passing the regression tests, I'm not making
+                # that change yet.
+                #
+                [obj, characterOffset] = self._getCaretPosition(obj)
+                self._setCaretPosition(obj, characterOffset)
+                self._presentObject(obj, characterOffset)
+        else:
+            # Translators: this is for navigating in document content
+            # by moving from form field to form field. This string is
+            # what Orca will say if there are no more form fields found.
+            #
+            speech.speak(_("No more form fields."))
+
+    ########################
+    #                      #
+    # Headings             #
+    #                      #
+    ########################
+
+    def _headingBindings(self):
+        bindings = {}
+        # Translators: this is for navigating in a document by heading.
+        # (e.g. <h1>)
+        #
+        prevDesc = _("Goes to previous heading.")
+        bindings["previous"] = ["h", shiftModMask, prevDesc]
+        # Translators: this is for navigating in a document by heading.
+        # (e.g., <h1>)
+        #
+        nextDesc = _("Goes to next heading.")
+        bindings["next"] = ["h", noModMask, nextDesc]
+
+        prevAtLevelBindings = []
+        nextAtLevelBindings = []
+        minLevel, maxLevel = self._headingLevels()
+        for i in range(minLevel, maxLevel + 1):
+            # Translators: this is for navigating in a document by heading.
+            # (e.g. <h1> is a heading at level 1).
+            #
+            prevDesc = _("Goes to previous heading at level %d." % i)
+            prevAtLevelBindings.append([str(i), shiftModMask, prevDesc])
+            # Translators: this is for navigating in a document by heading.
+            # (e.g. <h1> is a heading at level 1).
+            #
+            nextDesc = _("Goes to next heading at level %d." % i)
+            nextAtLevelBindings.append([str(i), noModMask, nextDesc])
+        bindings["previousAtLevel"] = prevAtLevelBindings
+        bindings["nextAtLevel"] = nextAtLevelBindings
+        return bindings
+
+    def _headingLevels(self):
+        return [1, 6]
+
+    def _headingCriteria(self, col, arg=None):
+        role = [pyatspi.ROLE_HEADING]
+        attrs = []
+        if arg:
+            attrs.append('level:%d' % arg)
+
+        return MatchCriteria(col,
+                             roles=role,
+                             objAttrs=attrs)
+
+    def _headingPredicate(self, obj, arg=None):
+        isMatch = False
+        if obj and obj.getRole() == pyatspi.ROLE_HEADING:
+            if arg:
+                isMatch = (arg == self._getHeadingLevel(obj))
+            else:
+                isMatch = True
+
+        return isMatch
+
+    def _headingPresentation(self, obj, arg=None):
+        if obj:
+            [obj, characterOffset] = self._getCaretPosition(obj)
+            self._setCaretPosition(obj, characterOffset)
+            self._presentObject(obj, characterOffset)
+        elif not arg:
+            # Translators: this is for navigating HTML content by
+            # moving from heading to heading (e.g. <h1>, <h2>, etc).
+            # This string is what Orca will say if there are no more
+            # headings found.
+            #
+            speech.speak(_("No more headings."))
+        else:
+            # Translators: this is for navigating HTML content by
+            # moving from heading to heading at a particular level
+            # (i.e. only <h1> or only <h2>, etc.) This string is
+            # what Orca will say if there are no more headings found.
+            #
+            speech.speak(_("No more headings at level %d.") % arg)
+
+    ########################
+    #                      #
+    # Landmarks            #
+    #                      #
+    ########################
+
+    def _landmarkBindings(self):
+        bindings = {}
+        # Translators: this is for navigating to the previous ARIA
+        # role landmark.  ARIA role landmarks are the W3C defined
+        # HTML tag attribute 'role' used to identify important part
+        # of webpage like banners, main context, search etc.
+        #
+        prevDesc = _("Goes to previous landmark.")
+        bindings["previous"] = ["", noModMask, prevDesc]
+        # Translators: this is for navigating to the next ARIA
+        # role landmark.  ARIA role landmarks are the W3C defined
+        # HTML tag attribute 'role' used to identify important part
+        # of webpage like banners, main context, search etc.
+        #
+        nextDesc = _("Goes to next landmark.")
+        bindings["next"] = ["", noModMask, nextDesc]
+        return bindings
+
+    def _landmarkCriteria(self, col, arg=None):
+        attrs = []
+        for landmark in self.ARIA_LANDMARKS:
+            attrs.append('xml-roles:' + landmark)
+
+        return MatchCriteria(col, objAttrs=attrs)
+
+    def _landmarkPredicate(self, obj, arg=None):
+        if obj is None:
+            return False
+
+        attrs = dict([attr.split(':', 1) for attr in obj.getAttributes()])
+        try:
+            if attrs['xml-roles'] in self.ARIA_LANDMARKS:
+                return True
+            else:
+                return False
+        except KeyError:
+            return False
+
+    def _landmarkPresentation(self, obj, arg=None):
+        if obj:
+            [obj, characterOffset] = self._getCaretPosition(obj)
+            self._setCaretPosition(obj, characterOffset)
+            self._presentObject(obj, characterOffset)
+        else:
+            # Translators: this is for navigating to the previous ARIA
+            # role landmark.  ARIA role landmarks are the W3C defined
+            # HTML tag attribute 'role' used to identify important part
+            # of webpage like banners, main context, search etc.  This
+            # is an indication that one was not found.
+            #
+            speech.speak(_("No landmark found."))
+
+    ########################
+    #                      #
+    # Lists                #
+    #                      #
+    ########################
+
+    def _listBindings(self):
+        bindings = {}
+        # Translators: this is for navigating among bulleted/numbered
+        # lists in a document.
+        #
+        prevDesc = _("Goes to previous list.")
+        bindings["previous"] = ["l", shiftModMask, prevDesc]
+        # Translators: this is for navigating among bulleted/numbered
+        # lists in a document.
+        #
+        nextDesc = _("Goes to next list.")
+        bindings["next"] = ["l", noModMask, nextDesc]
+        return bindings
+
+    def _listCriteria(self, col, arg=None):
+        role = [pyatspi.ROLE_LIST]
+        state = [pyatspi.STATE_FOCUSABLE]
+        stateMatch = col.MATCH_NONE
+        return MatchCriteria(col,
+                             states=state,
+                             matchStates=stateMatch,
+                             roles=role)
+
+    def _listPredicate(self, obj, arg=None):
+        isMatch = False
+
+        if obj and obj.getRole() == pyatspi.ROLE_LIST:
+            isMatch = not obj.getState().contains(pyatspi.STATE_FOCUSABLE)
+
+        return isMatch
+
+    def _listPresentation(self, obj, arg=None):
+        # TODO: Ultimately it should be the job of the speech (and braille)
+        # generator to present things like this.
+        #
+        if obj:
+            nItems = 0
+            for child in obj:
+                if child.getRole() == pyatspi.ROLE_LIST_ITEM:
+                    nItems += 1
+            # Translators: this represents a list in HTML.
+            #
+            itemString = ngettext("List with %d item",
+                                  "List with %d items",
+                                  nItems) % nItems
+            speech.speak(itemString)
+            nestingLevel = 0
+            parent = obj.parent
+            while parent.getRole() == pyatspi.ROLE_LIST:
+                nestingLevel += 1
+                parent = parent.parent
+            if nestingLevel:
+                # Translators: this represents a list item in a document.
+                # The nesting level is how 'deep' the item is (e.g., a
+                # level of 2 represents a list item inside a list that's
+                # inside another list).
+                #
+                speech.speak(_("Nesting level %d") % nestingLevel)
+            [obj, characterOffset] = self._getCaretPosition(obj)
+            self._setCaretPosition(obj, characterOffset)
+            self._presentLine(obj, characterOffset)
+        else:
+            # Translators: this is for navigating document content by moving
+            # from bulleted/numbered list to bulleted/numbered list. This
+            # string is what Orca will say if there are no more lists found.
+            #
+            speech.speak(_("No more lists."))
+
+    ########################
+    #                      #
+    # List Items           #
+    #                      #
+    ########################
+
+    def _listItemBindings(self):
+        bindings = {}
+        # Translators: this is for navigating among bulleted/numbered list
+        # items in a document.
+        #
+        prevDesc = _("Goes to previous list item.")
+        bindings["previous"] = ["i", shiftModMask, prevDesc]
+        # Translators: this is for navigating among bulleted/numbered list
+        # items in a document.
+        #
+        nextDesc = _("Goes to next list item.")
+        bindings["next"] = ["i", noModMask, nextDesc]
+        return bindings
+
+    def _listItemCriteria(self, col, arg=None):
+        role = [pyatspi.ROLE_LIST_ITEM]
+        state = [pyatspi.STATE_FOCUSABLE]
+        stateMatch = col.MATCH_NONE
+        return MatchCriteria(col,
+                             states=state,
+                             matchStates=stateMatch,
+                             roles=role)
+
+    def _listItemPredicate(self, obj, arg=None):
+        isMatch = False
+
+        if obj and obj.getRole() == pyatspi.ROLE_LIST_ITEM:
+            isMatch = not obj.getState().contains(pyatspi.STATE_FOCUSABLE)
+
+        return isMatch
+
+    def _listItemPresentation(self, obj, arg=None):
+        if obj:
+            [obj, characterOffset] = self._getCaretPosition(obj)
+            self._setCaretPosition(obj, characterOffset)
+            # TODO: We currently present the line, so that's kept here.
+            # But we should probably present the object, which would
+            # be consistent with the change made recently for headings.
+            #
+            self._presentLine(obj, characterOffset)
+        else:
+            # Translators: this is for navigating document content by
+            # moving from bulleted/numbered list item to  bulleted/
+            # numbered list item.  This string is what Orca will say
+            # if there are no more list items found.
+            #
+            speech.speak(_("No more list items."))
+
+    ########################
+    #                      #
+    # Live Regions         #
+    #                      #
+    ########################
+
+    def _liveRegionBindings(self):
+        bindings = {}
+        # Translators: this is for navigating between live regions
+        #
+        prevDesc = _("Goes to previous live region.")
+        bindings["previous"] = ["r", shiftModMask, prevDesc]
+        # Translators: this is for navigating between live regions
+        #
+        nextDesc = _("Goes to next live region.")
+        bindings["next"] = ["r", noModMask, nextDesc]
+        # Translators: this is for navigating to the last live region
+        # to make an announcement.
+        #
+        desc = _("Goes to last live region.")
+        bindings["last"] = ["y", noModMask, desc]
+        return bindings
+
+    def _liveRegionCriteria(self, col, arg=None):
+        return None
+
+    def _liveRegionPredicate(self, obj, arg=None):
+        isMatch = False
+
+        regobjs = self._script.liveMngr.getLiveNoneObjects()
+        if self._script.liveMngr.matchLiveRegion(obj) or obj in regobjs:
+            isMatch = True
+
+        return isMatch
+
+    def _liveRegionPresentation(self, obj, arg=None):
+        if obj:
+            # TODO: We don't want to move to a list item.
+            # Is this the best place to handle this?
+            #
+            if obj.getRole() == pyatspi.ROLE_LIST:
+                characterOffset = 0
+            else:
+                [obj, characterOffset] = self._getCaretPosition(obj)
+            self._setCaretPosition(obj, characterOffset)
+            self._presentObject(obj, characterOffset)
+            # For debugging
+            #
+            self._script.outlineAccessible(obj)
+        else:
+            # Translators: this is for navigating HTML in a structural
+            # manner, where a 'live region' is a location in a web page
+            # that are updated without having to refresh the entire page.
+            #
+            speech.speak(_("No more live regions."))
+
+    ########################
+    #                      #
+    # Radio Buttons        #
+    #                      #
+    ########################
+
+    def _radioButtonBindings(self):
+        bindings = {}
+        # Translators: this is for navigating among radio buttons in a
+        # form within a document.
+        #
+        prevDesc = _("Goes to previous radio button.")
+        bindings["previous"] = ["", noModMask, prevDesc]
+        # Translators: this is for navigating among radio buttons in a
+        # form within a document.
+        #
+        nextDesc = _("Goes to next radio button.")
+        bindings["next"] = ["", noModMask, nextDesc]
+        return bindings
+
+    def _radioButtonCriteria(self, col, arg=None):
+        role = [pyatspi.ROLE_RADIO_BUTTON]
+        state = [pyatspi.STATE_FOCUSABLE, pyatspi.STATE_SENSITIVE]
+        stateMatch = col.MATCH_ALL
+        return MatchCriteria(col,
+                             states=state,
+                             matchStates=stateMatch,
+                             roles=role)
+
+    def _radioButtonPredicate(self, obj, arg=None):
+        isMatch = False
+        if obj and obj.getRole() == pyatspi.ROLE_RADIO_BUTTON:
+            state = obj.getState()
+            isMatch = state.contains(pyatspi.STATE_FOCUSABLE) \
+                  and state.contains(pyatspi.STATE_SENSITIVE)
+
+        return isMatch
+
+    def _radioButtonPresentation(self, obj, arg=None):
+        if obj:
+            obj.queryComponent().grabFocus()
+        else:
+            # Translators: this is for navigating in document content
+            # by moving from radio button to radio button in a form.
+            # This string is what Orca will say if there are no more
+            # radio buttons found.
+            #
+            speech.speak(_("No more radio buttons."))
+
+    ########################
+    #                      #
+    # Tables               #
+    #                      #
+    ########################
+
+    def _tableBindings(self):
+        bindings = {}
+        # Translators: this is for navigating among tables in a document.
+        #
+        prevDesc = _("Goes to previous table.")
+        bindings["previous"] = ["t", shiftModMask, prevDesc]
+        # Translators: this is for navigating among tables in a document.
+        #
+        nextDesc = _("Goes to next table.")
+        bindings["next"] = ["t", noModMask, nextDesc]
+        return bindings
+
+    def _tableCriteria(self, col, arg=None):
+        role = [pyatspi.ROLE_TABLE]
+        return MatchCriteria(col, roles=role)
+
+    def _tablePredicate(self, obj, arg=None):
+        return (obj and obj.getRole() == pyatspi.ROLE_TABLE)
+
+    def _tablePresentation(self, obj, arg=None):
+        if obj:
+            caption = self._getTableCaption(obj)
+            if caption:
+                speech.speak(caption)
+            speech.speak(self._getTableDescription(obj))
+            cell = obj.queryTable().getAccessibleAt(0, 0)
+            self.lastTableCell = [0, 0]
+            [cell, characterOffset] = self._getCaretPosition(cell)
+            self._setCaretPosition(cell, characterOffset)
+            self._presentObject(cell, characterOffset)
+        else:
+            # Translators: this is for navigating document content by moving
+            # from table to table.  This string is what Orca will say if there
+            # are no more tables found.
+            #
+            speech.speak(_("No more tables."))
+
+    ########################
+    #                      #
+    # Table Cells          #
+    #                      #
+    ########################
+
+    def _tableCellBindings(self):
+        bindings = {}
+        # Translators: this is for navigating among table cells in a document.
+        #
+        desc = _("Goes left one cell.")
+        bindings["left"] = ["Left", shiftAltModMask, desc]
+        # Translators: this is for navigating among table cells in a document.
+        #
+        desc = _("Goes right one cell.")
+        bindings["right"] = ["Right", shiftAltModMask, desc]
+        # Translators: this is for navigating among table cells in a document.
+        #
+        desc = _("Goes up one cell.")
+        bindings["up"] = ["Up", shiftAltModMask, desc]
+        # Translators: this is for navigating among table cells in a document.
+        #
+        desc = _("Goes down one cell.")
+        bindings["down"] = ["Down", shiftAltModMask, desc]
+        # Translators: this is for navigating among table cells in a document.
+        #
+        desc = _("Goes to the first cell in a table.")
+        bindings["first"] = ["Home", shiftAltModMask, desc]
+        # Translators: this is for navigating among table cells in a document.
+        #
+        desc = _("Goes to the last cell in a table.")
+        bindings["last"] = ["End", shiftAltModMask, desc]
+        return bindings
+
+    def _tableCellCriteria(self, col, arg=None):
+        role = [pyatspi.ROLE_TABLE_CELL]
+        return MatchCriteria(col, roles=role)
+
+    def _tableCellPredicate(self, obj, arg=None):
+        return (obj and obj.getRole() == pyatspi.ROLE_TABLE_CELL)
+
+    def _tableCellPresentation(self, cell, arg):
+        if not cell:
+            return
+
+        if self.speakCellHeaders:
+            self._presentCellHeaders(cell, arg)
+
+        [obj, characterOffset] = self._getCaretPosition(cell)
+        self._setCaretPosition(obj, characterOffset)
+        self._script.updateBraille(obj)
+
+        blank = self._isBlankCell(cell)
+        if not blank:
+            self._presentObject(obj, characterOffset)
+        else:
+            # Translators: "blank" is a short word to mean the
+            # user has navigated to an empty line.
+            #
+            speech.speak(_("blank"))
+
+        if self.speakCellCoordinates:
+            [row, col] = self.getCellCoordinates(cell)
+            # Translators: this represents the (row, col) position of
+            # a cell in a table.
+            #
+            speech.speak(_("Row %d, column %d.") % (row + 1, col + 1))
+
+        spanString = self._getCellSpanInfo(cell)
+        if spanString and self.speakCellSpan:
+            speech.speak(spanString)
+
+    ########################
+    #                      #
+    # Unvisited Links      #
+    #                      #
+    ########################
+
+    def _unvisitedLinkBindings(self):
+        bindings = {}
+        # Translators: this is for navigating among unvisited links in a
+        # document.
+        #
+        prevDesc = _("Goes to previous unvisited link.")
+        bindings["previous"] = ["u", shiftModMask, prevDesc]
+        # Translators: this is for navigating among unvisited links in a
+        # document.
+        #
+        nextDesc = _("Goes to next unvisited link.")
+        bindings["next"] = ["u", noModMask, nextDesc]
+        return bindings
+
+    def _unvisitedLinkCriteria(self, col, arg=None):
+        role = [pyatspi.ROLE_LINK]
+        state = [pyatspi.STATE_VISITED]
+        stateMatch = col.MATCH_NONE
+        return MatchCriteria(col,
+                             states=state,
+                             matchStates=stateMatch,
+                             roles=role)
+
+    def _unvisitedLinkPredicate(self, obj, arg=None):
+        isMatch = False
+
+        if obj and obj.getRole() == pyatspi.ROLE_LINK:
+            isMatch = not obj.getState().contains(pyatspi.STATE_VISITED)
+
+        return isMatch
+
+    def _unvisitedLinkPresentation(self, obj, arg=None):
+        if obj:
+            [obj, characterOffset] = self._getCaretPosition(obj)
+            self._setCaretPosition(obj, characterOffset)
+            self._presentObject(obj, characterOffset)
+        else:
+            # Translators: this is for navigating document content by
+            # moving from unvisited link to unvisited link. This string
+            # is what Orca will say if there are no more unvisited links
+            # found.
+            #
+            speech.speak(_("No more unvisited links."))
+
+    ########################
+    #                      #
+    # Visited Links        #
+    #                      #
+    ########################
+
+    def _visitedLinkBindings(self):
+        bindings = {}
+        # Translators: this is for navigating among visited links in a
+        # document.
+        #
+        prevDesc = _("Goes to previous visited link.")
+        bindings["previous"] = ["v", shiftModMask, prevDesc]
+        # Translators: this is for navigating among visited links in a
+        # document.
+        #
+        nextDesc = _("Goes to next visited link.")
+        bindings["next"] = ["v", noModMask, nextDesc]
+        return bindings
+
+    def _visitedLinkCriteria(self, col, arg=None):
+        role = [pyatspi.ROLE_LINK]
+        state = [pyatspi.STATE_VISITED]
+        stateMatch = col.MATCH_ANY
+        return MatchCriteria(col,
+                             states=state,
+                             matchStates=stateMatch,
+                             roles=role)
+
+    def _visitedLinkPredicate(self, obj, arg=None):
+        isMatch = False
+
+        if obj and obj.getRole() == pyatspi.ROLE_LINK:
+            isMatch = obj.getState().contains(pyatspi.STATE_VISITED)
+
+        return isMatch
+
+    def _visitedLinkPresentation(self, obj, arg=None):
+        if obj:
+            [obj, characterOffset] = self._getCaretPosition(obj)
+            self._setCaretPosition(obj, characterOffset)
+            self._presentObject(obj, characterOffset)
+        else:
+            # Translators: this is for navigating document content by
+            # moving from visited link to visited link. This string is
+            # what Orca will say if there are no more visited links
+            # found.
+            #
+            speech.speak(_("No more visited links."))
Index: src/orca/script.py
===================================================================
--- src/orca/script.py	(revision 3927)
+++ src/orca/script.py	(working copy)
@@ -46,6 +46,7 @@
 import orca_state
 import settings
 import speechgenerator
+import structural_navigation
 import where_am_I
 import bookmarks
 
@@ -76,6 +77,7 @@
         #
         self.presentIfInactive = True
 
+        self.structuralNavigation = self.getStructuralNavigation()
         self.inputEventHandlers = {}
         self.pointOfReference = {}
         self.setupInputEventHandlers()
@@ -184,6 +186,18 @@
         """
         return speechgenerator.SpeechGenerator(self)
 
+    def getEnabledStructNavTypes(self):
+        """Returns a list of the structural navigation object types
+        enabled in this script.
+        """
+        return []
+
+    def getStructuralNavigation(self):
+        """Returns the 'structural navigation' class for this script.
+        """
+        types = self.getEnabledStructNavTypes()
+        return structural_navigation.StructuralNavigation(self, types)
+
     def getWhereAmI(self):
         """Returns the "where am I" class for this script.
         """
@@ -336,9 +350,19 @@
 
         consumes = False
         if user_bindings:
-            consumes = user_bindings.getInputHandler(keyboardEvent) != None
+            handler = user_bindings.getInputHandler(keyboardEvent)
+            if handler \
+                 and handler.function in self.structuralNavigation.functions:
+                return self.structuralNavigation.enabled
+            else:
+                consumes = handler != None
         if not consumes:
-            consumes = self.keyBindings.getInputHandler(keyboardEvent) != None
+            handler = self.keyBindings.getInputHandler(keyboardEvent)
+            if handler \
+                 and handler.function in self.structuralNavigation.functions:
+                return self.structuralNavigation.enabled
+            else:
+                consumes = handler != None
         return consumes
 
     def processKeyboardEvent(self, keyboardEvent):
Index: src/orca/Makefile.am
===================================================================
--- src/orca/Makefile.am	(revision 3927)
+++ src/orca/Makefile.am	(working copy)
@@ -56,6 +56,7 @@
 	speechdispatcherfactory.py \
 	speechgenerator.py \
 	speechserver.py \
+	structural_navigation.py \
 	where_am_I.py
 
 orca_pythondir=$(pyexecdir)/orca
