Index: src/orca/Gecko.py
===================================================================
--- src/orca/Gecko.py	(revision 2199)
+++ src/orca/Gecko.py	(working copy)
@@ -765,7 +765,15 @@
              Script.goNextUnvisitedLink,
              Script.goPreviousUnvisitedLink,
              Script.goNextVisitedLink,
-             Script.goPreviousVisitedLink]
+             Script.goPreviousVisitedLink,
+             Script.goNextTable,
+             Script.goPreviousTable,
+             Script.goCellLeft,
+             Script.goCellRight,
+             Script.goCellUp,
+             Script.goCellDown,
+             Script.goCellFirst,
+             Script.goCellLast]
 
         if controlCaretNavigation:
             debug.println(debug.LEVEL_CONFIGURATION,
@@ -840,6 +848,36 @@
                 Script.goPreviousLine,
                 "Goes to previous line.")
 
+        self.inputEventHandlers["goCellLeftHandler"] = \
+            input_event.InputEventHandler(
+                Script.goCellLeft,
+                "Goes left one cell.")
+
+        self.inputEventHandlers["goCellRightHandler"] = \
+            input_event.InputEventHandler(
+                Script.goCellRight,
+                "Goes right one cell.")
+
+        self.inputEventHandlers["goCellDownHandler"] = \
+            input_event.InputEventHandler(
+                Script.goCellDown,
+                "Goes down one cell.")
+
+        self.inputEventHandlers["goCellUpHandler"] = \
+            input_event.InputEventHandler(
+                Script.goCellUp,
+                "Goes up one cell.")
+
+        self.inputEventHandlers["goCellFirstHandler"] = \
+            input_event.InputEventHandler(
+                Script.goCellFirst,
+                "Goes to the first cell in a table.")
+
+        self.inputEventHandlers["goCellLastHandler"] = \
+            input_event.InputEventHandler(
+                Script.goCellLast,
+                "Goes to the last cell in a table.")
+
         self.inputEventHandlers["goPreviousHeadingHandler"] = \
             input_event.InputEventHandler(
                 Script.goPreviousHeading,
@@ -890,6 +928,16 @@
                 Script.goNextVisitedLink,
                 "Goes to next visited link.")
 
+        self.inputEventHandlers["goPreviousTableHandler"] = \
+            input_event.InputEventHandler(
+                Script.goPreviousTable,
+                "Goes to previous table.")
+
+        self.inputEventHandlers["goNextTableHandler"] = \
+            input_event.InputEventHandler(
+                Script.goNextTable,
+                "Goes to next table.")
+
         self.inputEventHandlers["toggleCaretNavigationHandler"] = \
             input_event.InputEventHandler(
                 Script.toggleCaretNavigation,
@@ -928,45 +976,117 @@
         keyBindings.add(
             keybindings.KeyBinding(
                 "Right",
-                1 << atspi.Accessibility.MODIFIER_CONTROL,
+                (1 << atspi.Accessibility.MODIFIER_CONTROL \
+                 | 1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
                 0,
                 self.inputEventHandlers["goNextCharacterHandler"]))
 
         keyBindings.add(
             keybindings.KeyBinding(
                 "Left",
-                1 << atspi.Accessibility.MODIFIER_CONTROL,
+                (1 << atspi.Accessibility.MODIFIER_CONTROL \
+                 | 1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
                 0,
                 self.inputEventHandlers["goPreviousCharacterHandler"]))
 
         keyBindings.add(
             keybindings.KeyBinding(
                 "Right",
+                (1 << atspi.Accessibility.MODIFIER_CONTROL \
+                 | 1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
                 1 << atspi.Accessibility.MODIFIER_CONTROL,
-                1 << atspi.Accessibility.MODIFIER_CONTROL,
                 self.inputEventHandlers["goNextWordHandler"]))
 
         keyBindings.add(
             keybindings.KeyBinding(
                 "Left",
+                (1 << atspi.Accessibility.MODIFIER_CONTROL \
+                 | 1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
                 1 << atspi.Accessibility.MODIFIER_CONTROL,
-                1 << atspi.Accessibility.MODIFIER_CONTROL,
                 self.inputEventHandlers["goPreviousWordHandler"]))
 
         keyBindings.add(
             keybindings.KeyBinding(
                 "Up",
+                (1 << atspi.Accessibility.MODIFIER_CONTROL \
+                 | 1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
                 0,
-                0,
                 self.inputEventHandlers["goPreviousLineHandler"]))
 
         keyBindings.add(
             keybindings.KeyBinding(
                 "Down",
+                (1 << atspi.Accessibility.MODIFIER_CONTROL \
+                 | 1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
                 0,
-                0,
                 self.inputEventHandlers["goNextLineHandler"]))
 
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "Right",
+                (1 << atspi.Accessibility.MODIFIER_CONTROL \
+                 | 1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
+                (1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
+                self.inputEventHandlers["goCellRightHandler"]))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "Left",
+                (1 << atspi.Accessibility.MODIFIER_CONTROL \
+                 | 1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
+                (1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
+                self.inputEventHandlers["goCellLeftHandler"]))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "Up",
+                (1 << atspi.Accessibility.MODIFIER_CONTROL \
+                 | 1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
+                (1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
+                self.inputEventHandlers["goCellUpHandler"]))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "Down",
+                (1 << atspi.Accessibility.MODIFIER_CONTROL \
+                 | 1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
+                (1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
+                self.inputEventHandlers["goCellDownHandler"]))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "Home",
+                (1 << atspi.Accessibility.MODIFIER_CONTROL \
+                 | 1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
+                (1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
+                self.inputEventHandlers["goCellFirstHandler"]))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "End",
+                (1 << atspi.Accessibility.MODIFIER_CONTROL \
+                 | 1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
+                (1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT),
+                self.inputEventHandlers["goCellLastHandler"]))
+
         return keyBindings
 
     def getKeyBindings(self):
@@ -1072,6 +1192,24 @@
 
         keyBindings.add(
             keybindings.KeyBinding(
+                "t",
+                (1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT \
+                 | 1 << atspi.Accessibility.MODIFIER_CONTROL),
+                1 << atspi.Accessibility.MODIFIER_SHIFT,
+                self.inputEventHandlers["goPreviousTableHandler"]))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "t",
+                (1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT \
+                 | 1 << atspi.Accessibility.MODIFIER_CONTROL),
+                0,
+                self.inputEventHandlers["goNextTableHandler"]))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
                 "F12",
                 1 << settings.MODIFIER_ORCA,
                 1 << settings.MODIFIER_ORCA,
@@ -2337,20 +2475,79 @@
 
         return [0, 0]
 
-    def getCellHeaders(self, obj):
-        """Returns the [rowHeader, colHeader] of a ROLE_TABLE_CELL or
-        [None, None] if the headers cannot be found.
+    def isHeader(self, obj):
+        """Returns True if the table cell is a header"""
+
+        if obj.attributes:
+            for attribute in obj.attributes:
+                if attribute == "tag:TH":
+                    return True               
+
+    def getRowHeader(self, obj):
+        """Returns the row header of a ROLE_TABLE_CELL or None if the
+        header cannot be found.
         """
 
+        rowHeader = None
         parent = obj.parent
         if parent and parent.table:
             [row, col] = self.getCellCoordinates(obj)
-            rowHeader = parent.table.getRowDescription(row)
-            colHeader = parent.table.getColumnDescription(col)
-            return [rowHeader, colHeader]
+            # 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.
+            #
+            accRowHeader = parent.table.getRowHeader(row)
+            if accRowHeader:
+                rowHeader = atspi.Accessible.makeAccessible(accRowHeader)
+                rowHeader = rowHeader.text.getText(0, -1)
 
-        return [None, None]
+            # Headers that are strictly marked up with <th> do not seem
+            # to be exposed through get{Row, Column}Header.
+            #
+            else:
+                for col in range(0, parent.table.nColumns - 1):
+                    accCell = parent.table.getAccessibleAt(row, col)
+                    cell = atspi.Accessible.makeAccessible(accCell)
+                    if self.isHeader(cell) and cell.text:
+                        rowHeader = cell.text.getText(0, -1)
+                        break
 
+        return rowHeader
+
+    def getColumnHeader(self, obj):
+        """Returns the column header of a ROLE_TABLE_CELL or None if the
+        header cannot be found.
+        """
+
+        colHeader = None
+        parent = obj.parent
+        if parent and parent.table:
+            [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.
+            # If the column header is in a <thead>, we can get it with
+            # getColumnHeader.
+            #
+            accColHeader = parent.table.getColumnHeader(col)
+            if accColHeader:
+                colHeader = atspi.Accessible.makeAccessible(accColHeader)
+                colHeader = colHeader.text.getText(0, -1)
+
+            # Headers that are strictly marked up with <th> do not seem
+            # to be exposed through get{Row, Column}Header.
+            #
+            else:
+                for row in range(0, parent.table.nRows - 1):
+                    accCell = parent.table.getAccessibleAt(row, col)
+                    cell = atspi.Accessible.makeAccessible(accCell)
+                    if self.isHeader(cell) and cell.text:
+                        colHeader = cell.text.getText(0, -1)
+                        break
+
+        return colHeader
+
     def getTableCaption(self, obj):
         """Returns the ROLE_CAPTION object of a ROLE_TABLE object or None
         if the caption cannot be found.
@@ -2813,7 +3010,7 @@
         [currentObj, characterOffset] = self.getCaretContext()
 
         ancestors = []
-        nestableRoles = [rolenames.ROLE_LIST]
+        nestableRoles = [rolenames.ROLE_LIST, rolenames.ROLE_TABLE]
         obj = currentObj.parent
         while obj:
             ancestors.append(obj)
@@ -3563,8 +3760,8 @@
             for i in range(0, parent.childCount):
                 if parent.child(i).role == rolenames.ROLE_LIST_ITEM:
                     nItems += 1
-            itemString = ngettext("List with %d item", 
-                                  "List with %d items", 
+            itemString = ngettext("List with %d item",
+                                  "List with %d items",
                                   nItems) % nItems
             speech.speak(itemString)
             nestingLevel = 0
@@ -3591,8 +3788,8 @@
             for i in range(0, parent.childCount):
                 if parent.child(i).role == rolenames.ROLE_LIST_ITEM:
                     nItems += 1
-            itemString = ngettext("List with %d item", 
-                                  "List with %d items", 
+            itemString = ngettext("List with %d item",
+                                  "List with %d items",
                                   nItems) % nItems
             speech.speak(itemString)
             nestingLevel = 0
@@ -3692,6 +3889,197 @@
         else:
             speech.speak(_("No more visited links."))
 
+    def goPreviousTable(self, inputEvent):
+        [obj, characterOffset] = self.findPreviousRole([rolenames.ROLE_TABLE])
+        if obj:
+            nRows = obj.table.nRows
+            nColumns = obj.table.nColumns
+            rowString = ngettext("Table with %d row",
+                                 "Table with %d rows",
+                                  nRows) % nRows
+            colString = ngettext(" and %d column",
+                                 " and %d columns",
+                                  nColumns) % nColumns
+            speech.speak(rowString + colString)
+            caption = self.getTableCaption(obj)
+            if caption and caption.text:
+                speech.speak(caption.text.getText(0, -1))
+            cell = obj.table.getAccessibleAt(0, 0)
+            obj = atspi.Accessible.makeAccessible(cell)
+            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
+            self.setCaretPosition(obj, characterOffset)
+            self.updateBraille(obj)
+            contents = self.getObjectContentsAtOffset(obj, characterOffset)
+            firstCellContents = contents[0][0].text
+            if firstCellContents:
+                self.speakContents(contents)
+            else:
+                speech.speak(_("blank"))
+        else:
+            speech.speak(_("No more tables."))
+
+    def goNextTable(self, inputEvent):
+        [obj, characterOffset] = self.findNextRole([rolenames.ROLE_TABLE])
+        if obj:
+            nRows = obj.table.nRows
+            nColumns = obj.table.nColumns
+            rowString = ngettext("Table with %d row",
+                                 "Table with %d rows",
+                                  nRows) % nRows
+            colString = ngettext(" and %d column",
+                                 " and %d columns",
+                                  nColumns) % nColumns
+            speech.speak(rowString + colString)
+            caption = self.getTableCaption(obj)
+            if caption and caption.text:
+                speech.speak(caption.text.getText(0, -1))
+            cell = obj.table.getAccessibleAt(0, 0)
+            obj = atspi.Accessible.makeAccessible(cell)
+            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
+            self.setCaretPosition(obj, characterOffset)
+            self.updateBraille(obj)
+            contents = self.getObjectContentsAtOffset(obj, characterOffset)
+            firstCellContents = contents[0][0].text
+            if firstCellContents:
+                self.speakContents(contents)
+            else:
+                speech.speak(_("blank"))
+        else:
+            speech.speak(_("No more tables."))
+
+    def goCellLeft(self, inputEvent):
+        [obj, characterOffset] = self.getCaretContext()
+        while obj and (obj != obj.parent) and \
+              (obj.role != rolenames.ROLE_TABLE_CELL):
+            obj = obj.parent
+        if obj and (obj.role == rolenames.ROLE_TABLE_CELL):
+            [row, col] = self.getCellCoordinates(obj)
+            if col > 0:
+                cell = obj.parent.table.getAccessibleAt(row, col - 1)
+                obj = atspi.Accessible.makeAccessible(cell)
+                [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
+                colHeader = self.getColumnHeader(obj)
+                if colHeader and not self.isHeader(obj):
+                    speech.speak(colHeader)
+                self.setCaretPosition(obj, characterOffset)
+                self.updateBraille(obj)
+                self.speakContents( \
+                        self.getObjectContentsAtOffset(obj, characterOffset))
+            else:
+                speech.speak(_("Beginning of row."))
+        else:
+            speech.speak(_("Not in a table."))
+
+    def goCellRight(self, inputEvent):
+        [obj, characterOffset] = self.getCaretContext()
+        while obj and (obj != obj.parent) and \
+              (obj.role != rolenames.ROLE_TABLE_CELL):
+            obj = obj.parent
+        if obj and (obj.role == rolenames.ROLE_TABLE_CELL):
+            [row, col] = self.getCellCoordinates(obj)
+            if col < obj.parent.table.nColumns - 1:
+                cell = obj.parent.table.getAccessibleAt(row, col + 1)
+                obj = atspi.Accessible.makeAccessible(cell)
+                colHeader = self.getColumnHeader(obj)
+                if colHeader and not self.isHeader(obj):
+                    speech.speak(colHeader)
+                [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
+                self.setCaretPosition(obj, characterOffset)
+                self.updateBraille(obj)
+                self.speakContents( \
+                        self.getObjectContentsAtOffset(obj, characterOffset))
+            else:
+                speech.speak(_("End of row."))
+        else:
+            speech.speak(_("Not in a table."))
+
+    def goCellUp(self, inputEvent):
+        [obj, characterOffset] = self.getCaretContext()
+        while obj and (obj != obj.parent) and \
+              (obj.role != rolenames.ROLE_TABLE_CELL):
+            obj = obj.parent
+        if obj and (obj.role == rolenames.ROLE_TABLE_CELL):
+            [row, col] = self.getCellCoordinates(obj)
+            if row > 0:
+                cell = obj.parent.table.getAccessibleAt(row - 1, col)
+                obj = atspi.Accessible.makeAccessible(cell)
+                rowHeader = self.getRowHeader(obj)
+                if rowHeader and not self.isHeader(obj):
+                    speech.speak(rowHeader)
+                [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
+                self.setCaretPosition(obj, characterOffset)
+                self.updateBraille(obj)
+                self.speakContents( \
+                        self.getObjectContentsAtOffset(obj, characterOffset))
+            else:
+                speech.speak(_("Top of column."))
+        else:
+            speech.speak(_("Not in a table."))
+
+    def goCellDown(self, inputEvent):
+        [obj, characterOffset] = self.getCaretContext()
+        while obj and (obj != obj.parent) and \
+              (obj.role != rolenames.ROLE_TABLE_CELL):
+            obj = obj.parent
+        if obj and (obj.role == rolenames.ROLE_TABLE_CELL):
+            [row, col] = self.getCellCoordinates(obj)
+            if row < obj.parent.table.nRows - 1:
+                cell = obj.parent.table.getAccessibleAt(row + 1, col)
+                obj = atspi.Accessible.makeAccessible(cell)
+                rowHeader = self.getRowHeader(obj)
+                if rowHeader and not self.isHeader(obj):
+                    speech.speak(rowHeader)
+                [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
+                self.setCaretPosition(obj, characterOffset)
+                self.updateBraille(obj)
+                self.speakContents( \
+                        self.getObjectContentsAtOffset(obj, characterOffset))
+            else:
+                speech.speak(_("Bottom of column."))
+        else:
+            speech.speak(_("Not in a table."))
+
+    def goCellFirst(self, inputEvent):
+        [obj, characterOffset] = self.getCaretContext()
+        while obj and (obj != obj.parent) and \
+              (obj.role != rolenames.ROLE_TABLE):
+            obj = obj.parent
+        if obj and (obj.role == rolenames.ROLE_TABLE):
+            cell = obj.table.getAccessibleAt(0, 0)
+            obj = atspi.Accessible.makeAccessible(cell)
+            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
+            if obj:
+                [row, col] = self.getCellCoordinates(obj)
+                speech.speak(_("Row %d, column %d.") % (row + 1, col + 1))
+                self.setCaretPosition(obj, characterOffset)
+                self.updateBraille(obj)
+                self.speakContents( \
+                        self.getObjectContentsAtOffset(obj, characterOffset))
+
+        else:
+            speech.speak(_("Not in a table."))
+
+    def goCellLast(self, inputEvent):
+        [obj, characterOffset] = self.getCaretContext()
+        while obj and (obj != obj.parent) and \
+              (obj.role != rolenames.ROLE_TABLE):
+            obj = obj.parent
+        if obj and (obj.role == rolenames.ROLE_TABLE):
+            cell = obj.table.getAccessibleAt(obj.table.nRows - 1,
+                                             obj.table.nColumns - 1)
+            obj = atspi.Accessible.makeAccessible(cell)
+            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
+            if obj:
+                [row, col] = self.getCellCoordinates(obj)
+                speech.speak(_("Row %d, column %d.") % (row + 1, col + 1))
+                self.setCaretPosition(obj, characterOffset)
+                self.updateBraille(obj)
+                self.speakContents( \
+                        self.getObjectContentsAtOffset(obj, characterOffset))
+
+        else:
+            speech.speak(_("Not in a table."))
+
     def toggleCaretNavigation(self, inputEvent):
         """Toggles between Firefox native and Orca caret navigation."""
 
