Index: src/orca/Gecko.py
===================================================================
--- src/orca/Gecko.py	(revision 2178)
+++ src/orca/Gecko.py	(working copy)
@@ -761,7 +761,11 @@
              Script.goNextChunk,
              Script.goPreviousChunk,
              Script.goNextList,
-             Script.goPreviousList]
+             Script.goPreviousList,
+             Script.goNextUnvisitedLink,
+             Script.goPreviousUnvisitedLink,
+             Script.goNextVisitedLink,
+             Script.goPreviousVisitedLink]
 
         if controlCaretNavigation:
             debug.println(debug.LEVEL_CONFIGURATION,
@@ -866,6 +870,26 @@
                 Script.goNextList,
                 "Goes to next list.")
 
+        self.inputEventHandlers["goPreviousUnvisitedLinkHandler"] = \
+            input_event.InputEventHandler(
+                Script.goPreviousUnvisitedLink,
+                "Goes to previous unvisited link.")
+
+        self.inputEventHandlers["goNextUnvisitedLinkHandler"] = \
+            input_event.InputEventHandler(
+                Script.goNextUnvisitedLink,
+                "Goes to next unvisited link.")
+
+        self.inputEventHandlers["goPreviousVisitedLinkHandler"] = \
+            input_event.InputEventHandler(
+                Script.goPreviousVisitedLink,
+                "Goes to previous visited link.")
+
+        self.inputEventHandlers["goNextVisitedLinkHandler"] = \
+            input_event.InputEventHandler(
+                Script.goNextVisitedLink,
+                "Goes to next visited link.")
+
         self.inputEventHandlers["toggleCaretNavigationHandler"] = \
             input_event.InputEventHandler(
                 Script.toggleCaretNavigation,
@@ -1012,6 +1036,42 @@
 
         keyBindings.add(
             keybindings.KeyBinding(
+                "u",
+                (1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT \
+                 | 1 << atspi.Accessibility.MODIFIER_CONTROL),
+                1 << atspi.Accessibility.MODIFIER_SHIFT,
+                self.inputEventHandlers["goPreviousUnvisitedLinkHandler"]))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "u",
+                (1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT \
+                 | 1 << atspi.Accessibility.MODIFIER_CONTROL),
+                0,
+                self.inputEventHandlers["goNextUnvisitedLinkHandler"]))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "v",
+                (1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT \
+                 | 1 << atspi.Accessibility.MODIFIER_CONTROL),
+                1 << atspi.Accessibility.MODIFIER_SHIFT,
+                self.inputEventHandlers["goPreviousVisitedLinkHandler"]))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
+                "v",
+                (1 << atspi.Accessibility.MODIFIER_SHIFT \
+                 | 1 << atspi.Accessibility.MODIFIER_ALT \
+                 | 1 << atspi.Accessibility.MODIFIER_CONTROL),
+                0,
+                self.inputEventHandlers["goNextVisitedLinkHandler"]))
+
+        keyBindings.add(
+            keybindings.KeyBinding(
                 "F12",
                 1 << settings.MODIFIER_ORCA,
                 1 << settings.MODIFIER_ORCA,
@@ -3546,6 +3606,90 @@
         else:
             speech.speak(_("No more lists."))
 
+    def goPreviousUnvisitedLink(self, inputEvent):
+        # 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.
+        #
+        [currentObj, characterOffset] = self.getCaretContext()
+        containingLink = self.getContainingLink(currentObj)
+        if containingLink:
+            obj = self.findPreviousObject(containingLink)
+        else:
+            obj = self.findPreviousObject(currentObj)
+
+        while obj and \
+              (obj.role != rolenames.ROLE_LINK or \
+               (obj.role == rolenames.ROLE_LINK and \
+                obj.state.count(atspi.Accessibility.STATE_VISITED))):
+            obj = self.findPreviousObject(obj)
+        if obj:
+            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
+            self.setCaretPosition(obj, characterOffset)
+            self.updateBraille(obj)
+            self.speakContents(self.getObjectContentsAtOffset(obj,
+                                                              characterOffset))
+        else:
+            speech.speak(_("No more unvisited links."))
+
+    def goNextUnvisitedLink(self, inputEvent):
+        [obj, characterOffset] = self.getCaretContext()
+        obj = self.findNextObject(obj)
+        while obj and \
+              (obj.role != rolenames.ROLE_LINK or \
+               (obj.role == rolenames.ROLE_LINK and \
+                obj.state.count(atspi.Accessibility.STATE_VISITED))):
+            obj = self.findNextObject(obj)
+        if obj:
+            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
+            self.setCaretPosition(obj, characterOffset)
+            self.updateBraille(obj)
+            self.speakContents(self.getObjectContentsAtOffset(obj,
+                                                              characterOffset))
+        else:
+            speech.speak(_("No more unvisited links."))
+
+    def goPreviousVisitedLink(self, inputEvent):
+        # 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.
+        #
+        [currentObj, characterOffset] = self.getCaretContext()
+        containingLink = self.getContainingLink(currentObj)
+        if containingLink:
+            obj = self.findPreviousObject(containingLink)
+        else:
+            obj = self.findPreviousObject(currentObj)
+
+        while obj and \
+              (obj.role != rolenames.ROLE_LINK or not \
+               obj.state.count(atspi.Accessibility.STATE_VISITED)):
+            obj = self.findPreviousObject(obj)
+        if obj:
+            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
+            self.setCaretPosition(obj, characterOffset)
+            self.updateBraille(obj)
+            self.speakContents(self.getObjectContentsAtOffset(obj,
+                                                              characterOffset))
+        else:
+            speech.speak(_("No more visited links."))
+
+    def goNextVisitedLink(self, inputEvent):
+        [obj, characterOffset] = self.getCaretContext()
+        obj = self.findNextObject(obj)
+        while obj and \
+              (obj.role != rolenames.ROLE_LINK or not \
+               obj.state.count(atspi.Accessibility.STATE_VISITED)):
+            obj = self.findNextObject(obj)
+        if obj:
+            [obj, characterOffset] = self.findFirstCaretContext(obj, 0)
+            self.setCaretPosition(obj, characterOffset)
+            self.updateBraille(obj)
+            self.speakContents(self.getObjectContentsAtOffset(obj,
+                                                              characterOffset))
+        else:
+            speech.speak(_("No more visited links."))
+
     def toggleCaretNavigation(self, inputEvent):
         """Toggles between Firefox native and Orca caret navigation."""
 
