Index: src/orca/default.py
===================================================================
--- src/orca/default.py	(revision 2352)
+++ src/orca/default.py	(working copy)
@@ -4238,11 +4238,13 @@
 
         return displayedText
 
-    def getDisplayedText(self, obj):
+    def getDisplayedText(self, obj, startOffset=0, endOffset= -1):
         """Returns the text being displayed for an object.
 
         Arguments:
         - obj: the object
+        - startOffset: the offset of the first character to be included
+        - endOffset: the offset of the last character to be included
 
         Returns the text being displayed for an object or None if there isn't
         any text being shown.
@@ -4257,7 +4259,7 @@
         # drawn on the screen.
         #
         if obj.text:
-            displayedText = obj.text.getText(0, -1)
+            displayedText = obj.text.getText(startOffset, endOffset)
 
             # [[[WDW - HACK to account for things such as Gecko that want
             # to use the EMBEDDED_OBJECT_CHARACTER on a label to hold the
@@ -4274,17 +4276,23 @@
                     displayedText = self.getDisplayedText(obj.child(0))
                 except:
                     debug.printException(debug.LEVEL_WARNING)
-            elif unicodeText:
-                # [[[TODO: HACK - Welll.....we'll just plain ignore any
-                # text with EMBEDDED_OBJECT_CHARACTERs here.  We still need a
-                # general case to handle this stuff and expand objects
-                # with EMBEDDED_OBJECT_CHARACTERs.]]]
-                #
-                for i in range(0, len(unicodeText)):
-                    if unicodeText[i] == self.EMBEDDED_OBJECT_CHARACTER:
-                        displayedText = None
-                        break
 
+            # Check for text with EMBEDDED_OBJECT_CHARACTERs. If we find
+            # some, we'll build a new string by expanding them.
+            #
+            elif unicodeText and \
+                 self.EMBEDDED_OBJECT_CHARACTER in unicodeText:
+                toBuild = list(unicodeText)
+                count = toBuild.count(self.EMBEDDED_OBJECT_CHARACTER)
+                for i in range(0, count):
+                    index = toBuild.index(self.EMBEDDED_OBJECT_CHARACTER)
+                    child = obj.child(i)
+                    childText = self.getDisplayedText(child)
+                    if not childText:
+                        childText = ""
+                    toBuild[index] = childText.decode("UTF-8")
+                displayedText = "".join(toBuild)
+
         if (not displayedText) or (len(displayedText) == 0):
             displayedText = obj.name
 
Index: src/orca/Gecko.py
===================================================================
--- src/orca/Gecko.py	(revision 2352)
+++ src/orca/Gecko.py	(working copy)
@@ -492,22 +492,33 @@
         Returns a list of utterances to be spoken for the object.
         """
 
+        utterances = []
         parent = obj.parent
-        if parent.role != rolenames.ROLE_AUTOCOMPLETE:
+        if parent.role == rolenames.ROLE_AUTOCOMPLETE:
+            # This is the main difference between this class and the default
+            # class - we'll give this thing a name here, and we'll make it
+            # be the name of the autocomplete.
+            #
+            label = self._script.getDisplayedLabel(parent)
+            if not label or not len(label):
+                label = parent.name
+            utterances.append(label)
+        elif obj.role in [rolenames.ROLE_ENTRY,
+                          rolenames.ROLE_PASSWORD_TEXT] and \
+             self._script.inDocumentContent():
+            # This is a form field in web content.  If we don't get a label,
+            # we'll try to guess what text on the page is functioning as
+            # the label.
+            #
+            label = self._script.getDisplayedLabel(obj)
+            if not label or not len(label):
+                label = self._script.guessTheLabel(obj)
+            if label:
+                utterances.append(label)
+        else:
             return speechgenerator.SpeechGenerator._getSpeechForText(
                 self, obj, already_focused)
 
-        utterances = []
-
-        # This is the main difference between this class and the default
-        # class - we'll give this thing a name here, and we'll make it
-        # be the name of the autocomplete.
-        #
-        label = self._script.getDisplayedLabel(parent)
-        if not label or not len(label):
-            label = parent.name
-        utterances.append(label)
-
         utterances.extend(self._getSpeechForObjectRole(obj))
 
         [text, caretOffset, startOffset] = self._script.getTextLineAtCaret(obj)
@@ -540,8 +551,11 @@
         # we'll fall back to the accessible name.
         #
         label = self._script.getDisplayedLabel(obj)
-        if not label and not self._script.inDocumentContent():
-            label = obj.name
+        if not label:
+            if not self._script.inDocumentContent():
+                label = obj.name
+            else:
+                label = self._script.guessTheLabel(obj)
 
         if not already_focused and label:
             utterances.append(label)
@@ -758,7 +772,14 @@
 
         utterances = []
 
-        if not obj:
+        # [[[TODO - JD: Right now we're returning immediately if we're
+        # not in the document frame to minimize chattiness that is
+        # resulting from a change in default.getDisplayedText() which
+        # added handling for text with EMBEDDED_OBJECT_CHARACTERs.
+        # Returning immediately may or may not be what we actually want
+        # to do.]]]
+        #
+        if not obj or self._script.inDocumentContent():
             return utterances
 
         if obj is stopAncestor:
@@ -3755,6 +3776,381 @@
 
         return level
 
+    def getNextCellInfo(self, cell, direction):
+        """Given a cell from which to start and a direction in which to
+        search locates the next cell and returns it, along with its
+        text, extents (as a tuple), and whether or not the cell contents
+        consist of a form field.
+
+        Arguments
+        - cell: the table cell from which to start
+        - direction: a string which can be one of four options: 'left',
+                     'right', 'up', 'down'
+
+        Returns [nextCell, text, extents, isField]
+        """
+
+        newCell = None
+        text = None
+        extents = ()
+        isField = False
+        if not cell or cell.role != rolenames.ROLE_TABLE_CELL:
+            return [newCell, text, extents, isField]
+
+        [row, col] = self.getCellCoordinates(cell)
+        rowspan = cell.parent.table.getRowExtentAt(row, col)
+        colspan = cell.parent.table.getColumnExtentAt(row, col)
+        nRows = cell.parent.table.nRows
+        nCols = cell.parent.table.nColumns
+        nextCell = None
+        if direction == "left" and col > 0:
+            nextCell = (row, col - 1)
+        elif direction == "right" and (col + colspan <= nCols - 1):
+            nextCell = (row, col + colspan)
+        elif direction == "up" and row > 0:
+            nextCell = (row - 1, col)
+        elif direction == "down" and (row + rowspan <= nRows - 1):
+            nextCell = (row + rowspan, col)
+        if nextCell:
+            accCell = cell.parent.table.getAccessibleAt(nextCell[0],
+                                                        nextCell[1])
+            newCell = atspi.Accessible.makeAccessible(accCell)
+            if newCell:
+                [obj, offset] = self.findFirstCaretContext(newCell, 0)
+                extents = self.getExtents(newCell, 0, 1)
+                isField = self.isFormField(obj)
+                if newCell.text and not self.isBlankCell(newCell):
+                    text = self.getDisplayedText(newCell)
+
+        return [newCell, text, extents, isField]
+
+    def guessLabelFromLine(self, obj):
+        """Attempts to guess what the label of an unlabeled form control
+        might be by looking at surrounding contents from the same line.
+
+        Arguments
+        - obj: the form field about which to take a guess
+
+        Returns the text which we think might be the label or None if we
+        give up.
+        """
+
+        # Based on Tom Brunet's comments on how Home Page Reader
+        # approached the task of guessing labels.  Please see:
+        # https://bugzilla.mozilla.org/show_bug.cgi?id=376481#c15
+        #
+        #  1. Text/img that precedes control in same item.
+        #  2. Text/img that follows control in same item (nothing between
+        #     end of text and item)
+        #
+        guess = None
+        extents = obj.component.getExtents(0)
+        objExtents = [extents.x, extents.y, extents.width, extents.height]
+        lineContents = self.getLineContentsAtOffset(obj, 0)
+        extents = obj.component.getExtents(0)
+        objExtents = [extents.x, extents.y, extents.width, extents.height]
+        # Let's figure out where we are with respect to the other objects
+        # on this line.
+        #
+        ourIndex = -1
+        objectsOnLine = []
+        for content in lineContents:
+            objectsOnLine.append(content[0])
+            extents = content[0].component.getExtents(0)
+            contentExtents = \
+                     [extents.x, extents.y, extents.width, extents.height]
+            if objExtents == contentExtents:
+                ourIndex = lineContents.index(content)
+        # Now that we know where we are, let's see who are neighbors are
+        # and where they are.
+        #
+        onLeft = None
+        onRight = None
+        if ourIndex > 0:
+            onLeft = objectsOnLine[ourIndex - 1]
+            extents = content[0].component.getExtents(0)
+            onLeftExtents = \
+                     [extents.x, extents.y, extents.width, extents.height]
+        if 0 <= ourIndex < len(objectsOnLine) - 1:
+            onRight = objectsOnLine[ourIndex + 1]
+            extents = content[0].component.getExtents(0)
+            onRightExtents = \
+                     [extents.x, extents.y, extents.width, extents.height]
+
+        # [[[TODO - JD: We presumably need to take direction of the text
+        # into account because the label in a ltr language is likely to
+        # be on the left, whereas in a rtl language it is likely to be
+        # on the right.  I have yet to figure out how this information
+        # gets exposed, so we're not doing that yet. :-)]]]
+        #
+        if onLeft and not self.isFormField(onLeft):
+            # We only want to speak the text up to this control.
+            #
+            offset = self.getCharacterOffsetInParent(obj)
+            if not offset:
+                offset = -1
+            guess = self.getDisplayedText(onLeft, 0, offset)
+            if not guess and onLeft.role == rolenames.ROLE_IMAGE:
+                guess = onLeft.name
+        elif onRight and not self.isFormField(onRight):
+            # The object's horizontal position plus its width tells us
+            # where the text on the right can begin.  For now, define
+            # "immediately after" as  within 50 pixels.
+            #
+            canStartAt = objExtents[0] + objExtents[2]
+            if (onRightExtents[0] - canStartAt) <= 50:
+                # We only want to speak the text up to this control.
+                # Note: This is to address rtl languages.  I don't know
+                # if it actually works yet.
+                #
+                offset = self.getCharacterOffsetInParent(obj)
+                if not offset:
+                    offset = -1
+                guess = self.getDisplayedText(onRight, 0, offset)
+                if not guess and onRight.role == rolenames.ROLE_IMAGE:
+                    guess = onRight.name
+
+        return guess
+    
+    def guessLabelFromOtherLines(self, obj):
+        """Attempts to guess what the label of an unlabeled form control
+        might be by looking at nearby contents from neighboring lines.
+
+        Arguments
+        - obj: the form field about which to take a guess
+
+        Returns the text which we think might be the label or None if we
+        give up.
+        """
+
+        # Based on Tom Brunet's comments on how Home Page Reader
+        # approached the task of guessing labels.  Please see:
+        # https://bugzilla.mozilla.org/show_bug.cgi?id=376481#c15
+        #
+        guess = None
+        extents = obj.component.getExtents(0)
+        objExtents = \
+               [extents.x, extents.y, extents.width, extents.height]
+        [prevObj, prevOffset] = self.findPreviousLine(obj, 0)
+        prevLineContents = self.getLineContentsAtOffset(prevObj,
+                                                        prevOffset)
+        [nextObj, nextOffset] = self.findNextLine(obj, 0)
+        nextLineContents = self.getLineContentsAtOffset(nextObj,
+                                                        nextOffset)
+        above = None
+        for content in prevLineContents:
+            extents = content[0].component.getExtents(0)
+            aboveExtents = \
+                 [extents.x, extents.y, extents.width, extents.height]
+            # If the horizontal starting point of the object is the
+            # same as the horizontal starting point of the text
+            # above it, the text above it is probably serving as the
+            # label. We'll allow for a 1 pixel difference.
+            #
+            if abs(objExtents[0] - aboveExtents[0]) <= 1 and \
+               not self.isFormField(content[0]):
+                above = content[0]
+                guessAbove = self.getDisplayedText(content[0],
+                                                   content[1],
+                                                   content[2])
+                break
+
+        below = None
+        for content in nextLineContents:
+            extents = content[0].component.getExtents(0)
+            belowExtents = \
+                 [extents.x, extents.y, extents.width, extents.height]
+            # If the horizontal starting point of the object is the
+            # same as the horizontal starting point of the text
+            # below it, the text below it is probably serving as the
+            # label. We'll allow for a 1 pixel difference.
+            #
+            if abs(objExtents[0] - belowExtents[0]) <= 1 and \
+               not self.isFormField(content[0]):
+                below = content[0]
+                guessBelow = self.getDisplayedText(content[0],
+                                                   content[1],
+                                                   content[2])
+                break
+
+        if above:
+            if not below:
+                guess = guessAbove
+            else:
+                # We'll guess the nearest text.
+                #
+                bottomOfAbove = aboveExtents[1] + aboveExtents[3]
+                topOfBelow = belowExtents[1]
+                bottomOfObj = objExtents[1] + objExtents[3]
+                topOfObject = objExtents[1]
+                aboveProximity = topOfObject - bottomOfAbove
+                belowProximity = topOfBelow - bottomOfObj
+                if aboveProximity <=  belowProximity:
+                    guess = guessAbove
+                else:
+                    guess = guessBelow
+        elif below:
+            guess = guessBelow
+
+        return guess
+
+    def guessLabelFromTable(self, obj):
+        """Attempts to guess what the label of an unlabeled form control
+        might be by looking at surrounding table cells.
+
+        Arguments
+        - obj: the form field about which to take a guess
+
+        Returns the text which we think might be the label or None if we
+        give up.
+        """
+
+        # Based on Tom Brunet's comments on how Home Page Reader
+        # approached the task of guessing labels.  Please see:
+        # https://bugzilla.mozilla.org/show_bug.cgi?id=376481#c15
+        #
+        # "3. Text/img that precedes control in previous item/cell
+        #     not another control in that item)..."
+        #
+        #  4. Text/img in cell above without other controls in this
+        #     cell above."
+        #
+        # If that fails, we might as well look to the cell below. If the
+        # text is immediately below the entry and nothing else looks like
+        # a label, that text might be it. Given both text above and below
+        # the control, the most likely label is probably the text that is
+        # vertically nearest it. This theory will, of course, require
+        # testing "in the wild."
+        #
+        guess = None
+        extents = obj.component.getExtents(0)
+        objExtents = [extents.x, extents.y, extents.width, extents.height]
+        containingCell = \
+                       self.getContainingRole(obj, rolenames.ROLE_TABLE_CELL)
+        if not containingCell:
+            return guess
+
+        [cellLeft, leftText, leftExtents, leftIsField] = \
+                   self.getNextCellInfo(containingCell, "left")
+        [cellRight, rightText, rightExtents, rightIsField] = \
+                   self.getNextCellInfo(containingCell, "right")
+        [cellAbove, aboveText, aboveExtents, aboveIsField] = \
+                   self.getNextCellInfo(containingCell, "up")
+        [cellBelow, belowText, belowExtents, belowIsField] = \
+                   self.getNextCellInfo(containingCell, "down")
+
+        if rightText:
+            # The object's horizontal position plus its width tells us
+            # where the text on the right can begin. For now, define
+            # "immediately after" as  within 50 pixels.
+            #
+            canStartAt = objExtents[0] + objExtents[2]
+            rightCloseEnough = rightExtents[0] - canStartAt <= 50
+
+        if leftText and not leftIsField:
+            guess = leftText
+        elif rightText and rightCloseEnough and not rightIsField:
+            guess = rightText
+        elif aboveText and not aboveIsField:
+            if not belowText or belowIsField:
+                guess = aboveText
+            else:
+                # We'll guess the nearest text.
+                #
+                bottomOfAbove = aboveExtents[1] + aboveExtents[3]
+                topOfBelow = belowExtents[1]
+                bottomOfObj = objExtents[1] + objExtents[3]
+                topOfObject = objExtents[1]
+                aboveProximity = topOfObject - bottomOfAbove
+                belowProximity = topOfBelow - bottomOfObj
+                if aboveProximity <=  belowProximity:
+                    guess = aboveText
+                else:
+                    guess = belowText
+        elif belowText and not belowIsField:
+            guess = belowText
+        elif aboveIsField:
+            # Given the lack of potential labels and the fact that
+            # there's a form field immediately above us, there's
+            # a reasonable chance that we're in a series of form
+            # fields arranged grid-style.  It's even more likely
+            # if the form fields above us are all of the same type
+            # all size (say, within 1 pixel).
+            #
+            grid = False
+            done = False
+            nextCell = containingCell
+            while not done:
+                [nextCell, text, extents, isField] = \
+                        self.getNextCellInfo(nextCell, "up")
+                dWidth = abs(objExtents[2] - extents[2])
+                dHeight = abs(objExtents[3] - extents[3])
+                if dWidth > 1 or dHeight > 1:
+                    done = True
+                if not isField:
+                    [row, col]=  self.getCellCoordinates(nextCell)
+                    if row == 0:
+                        grid = True
+                        done = True
+
+            if grid:
+                cell = nextCell.parent.table.getAccessibleAt(0, col)
+                topCol = atspi.Accessible.makeAccessible(cell)
+                [objTop, offset] = self.findFirstCaretContext(topCol, 0)
+                if topCol.text and not self.isFormField(topCol):
+                    guess = self.getDisplayedText(topCol)
+
+        return guess
+
+    def guessTheLabel(self, obj):
+        """Attempts to guess what the label of an unlabeled form control
+        might be.
+
+        Arguments
+        - obj: the form field about which to take a guess
+
+        Returns the text which we think might be the label or None if we
+        give up.
+        """
+
+        # The initial stab at this is based on Tom Brunet's comments
+        # on how Home Page Reader approached this task.  His comments
+        # can be found at the RFE for Mozilla to do this work for us:
+        # https://bugzilla.mozilla.org/show_bug.cgi?id=376481#c15
+        # N.B. If you see a comment in quotes, it's taken directly from
+        # Tom.
+        #
+        guess = None
+
+        # If we're not in the document frame, we don't want to be guessing.
+        #
+        if not self.inDocumentContent():
+            return guess
+
+        if obj.role in [rolenames.ROLE_ENTRY,
+                        rolenames.ROLE_PASSWORD_TEXT,
+                        rolenames.ROLE_COMBO_BOX]:
+            guess = self.guessLabelFromLine(obj)
+            if not guess:
+                # Maybe it's in a table cell.
+                #
+                guess = self.guessLabelFromTable(obj)
+            if not guess:
+                # Maybe the text is above or below us, but not in a table
+                # cell.
+                #
+                guess = self.guessLabelFromOtherLines(obj)
+            if not guess:
+                # We've pretty much run out of options.  From Tom's overview
+                # of the approach for all controls:
+                # "... 4. title attribute."
+                # The title attribute seems to be exposed as the name.
+                #
+                guess = obj.name
+
+        # print "guessTheLabel(): ", guess
+        return guess
+
     ####################################################################
     #                                                                  #
     # Methods to find previous and next objects.                       #
