Index: src/orca/Gecko.py
===================================================================
--- src/orca/Gecko.py	(revision 2261)
+++ src/orca/Gecko.py	(working copy)
@@ -43,6 +43,7 @@
 import settings
 import speech
 import speechgenerator
+import speechserver
 
 from orca_i18n import _
 from orca_i18n import ngettext  # for ngettext support
@@ -1569,6 +1570,114 @@
                 consumes = handler != None
         return consumes
 
+    def textLines(self, obj):
+        """Creates a generator that can be used to iterate over each line
+        of a text object, starting at the caret offset.
+
+        Arguments:
+        - obj: an Accessible that has a text specialization
+
+        Returns an iterator that produces elements of the form:
+        [SayAllContext, acss], where SayAllContext has the text to be
+        spoken and acss is an ACSS instance for speaking the text.
+        """
+
+        if not obj or not obj.text:
+            return
+
+        offset = obj.text.caretOffset
+        endOffset = -1
+        usableTextRoles = NEWLINE_ROLES
+        usableTextRoles.extend([rolenames.ROLE_LINK])
+        if obj.role in usableTextRoles or \
+           obj.parent.role == rolenames.ROLE_SECTION:
+            # We're in some text we can work with.  Try to find a sentence
+            # boundary ourselves as Firefox doesn't support that type of
+            # text boundary.
+            #
+            [line, startOffset, endOffset] = obj.text.getTextAtOffset(offset,
+                                 atspi.Accessibility.TEXT_BOUNDARY_LINE_START)
+            terminators = ['. ', '? ', '! ']
+            beginAt = 0
+            for terminator in terminators:
+                try:
+                    index = line.rindex(terminator, 0, offset - startOffset)
+                    if index > beginAt:
+                        beginAt = index
+                except:
+                    pass
+            offset = startOffset + beginAt
+
+        done = False
+        while not done:
+            contents = self.getObjectContentsAtOffset(obj, offset)
+            for content in contents:
+                [obj, startOffset, endOffset] = content
+                utterances = self.getUtterancesFromContents([content])
+                for utterance in utterances:
+                    string = utterance[0]
+                    voice = utterance[1]
+                    # If the object is the first child of a list item,
+                    # prepend the number/bullet.
+                    #
+                    if utterances.index(utterance) == 0 and \
+                       obj.parent.role == rolenames.ROLE_LIST_ITEM and \
+                       obj.index == 0:
+                        itemOffset = self.getCharacterOffsetInParent(obj)
+                        listNumber = \
+                              self.getText(obj.parent, 0, itemOffset)
+                        string = listNumber + " " + string
+                    # If the object is a list item, and the list item has
+                    # no children, prepend the number/bullet.
+                    #
+                    if obj.role == rolenames.ROLE_LIST_ITEM and \
+                       obj.childCount == 0:
+                        listNumber = self.getText(obj, 0, startOffset)
+                        string = listNumber + " " + string
+
+                    # Attempt to keep the content visible on the screen as
+                    # it is being read, but avoid links as grabFocus sometimes
+                    # makes them disappear and sayAll to subsequently stop.
+                    #
+                    if obj.role in [rolenames.ROLE_HEADING,
+                                    rolenames.ROLE_SECTION,
+                                    rolenames.ROLE_PARAGRAPH]:
+                        try:
+                            self.setCaretPosition(obj, endOffset)
+                        except:
+                            self.setCaretPosition(obj, endOffset-1)
+                    yield [speechserver.SayAllContext(obj, string,
+                                                      startOffset, endOffset),
+                           voice]
+
+            moreLines = False
+            lastObj = obj
+            lastEndOffset = endOffset
+            while obj and not moreLines:
+                [obj, offset] = \
+                       self.findNextCaretInOrder(obj, endOffset)
+                if obj == lastObj and lastEndOffset == endOffset:
+                    obj = self.findNextObject(obj)
+                if obj and obj.text:
+                    moreLines = True
+                    [obj, offset] = self.findFirstCaretContext(obj, offset)
+            if not moreLines:
+                done = True
+
+    def __sayAllProgressCallback(self, context, type):
+        if type == speechserver.SayAllContext.PROGRESS:
+            #print "PROGRESS", context.utterance, context.currentOffset
+            pass
+        elif type == speechserver.SayAllContext.INTERRUPTED:
+            #print "INTERRUPTED", context.utterance, context.currentOffset
+            self.setCaretPosition(context.obj, context.currentOffset)
+            self.updateBraille(context.obj)
+        elif type == speechserver.SayAllContext.COMPLETED:
+            #print "COMPLETED", context.utterance, context.currentOffset
+            orca.setLocusOfFocus(None, context.obj, False)
+            self.setCaretPosition(context.obj, context.currentOffset-1)
+            self.updateBraille(context.obj)
+
     def onCaretMoved(self, event):
         """Caret movement in Gecko is somewhat unreliable and
         unpredictable, but we need to handle it.  When we detect caret
