Index: src/orca/default.py
===================================================================
--- src/orca/default.py	(revision 2345)
+++ src/orca/default.py	(working copy)
@@ -4224,11 +4224,12 @@
 
         return displayedText
 
-    def getDisplayedText(self, obj):
+    def getDisplayedText(self, obj, endOffset= -1):
         """Returns the text being displayed for an object.
 
         Arguments:
         - obj: the object
+        - 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.
@@ -4243,7 +4244,7 @@
         # drawn on the screen.
         #
         if obj.text:
-            displayedText = obj.text.getText(0, -1)
+            displayedText = obj.text.getText(0, endOffset)
 
             # [[[WDW - HACK to account for things such as Gecko that want
             # to use the EMBEDDED_OBJECT_CHARACTER on a label to hold the
@@ -4260,17 +4261,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 2345)
+++ src/orca/Gecko.py	(working copy)
@@ -491,22 +491,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)
@@ -831,7 +842,14 @@
 
         utterances.reverse()
 
-        return utterances
+        # Note, due to a change in getDisplayedText() to handle EOC's
+        # Orca got rather chatty.  For now, in order to test the new
+        # label guessing functionality, this routine is returning
+        # nothing EVER.  Will and I need to talk about what should
+        # happen/be done differently. --jd
+        #
+        return []
+#       return utterances
 
 ########################################################################
 #                                                                      #
@@ -3716,6 +3734,225 @@
 
         return level
 
+    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
+
+        # "For text inputs:
+        #  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)
+        #  3. Text/img that precedes control in previous item/cell (not
+        #     another control in that item)..."
+        #
+        # Note: I'm not convinced item #3 is necessary given the approach
+        # taken below.  By using getLineContentsAtOffset(), whether something
+        # is in a table cell or not is largely irrelevant. I think....
+        #
+        if obj.role in [rolenames.ROLE_ENTRY,
+                        rolenames.ROLE_PASSWORD_TEXT]:
+            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 the entry.
+                #
+                offset = self.getCharacterOffsetInParent(obj)
+                if not offset:
+                    offset = -1
+                guess = self.getDisplayedText(onLeft, 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 the entry.
+                    #
+                    offset = self.getCharacterOffsetInParent(obj)
+                    if not offset:
+                        offset = -1
+                    guess = self.getDisplayedText(onRight, offset)
+                    if not guess and onRight.role == rolenames.ROLE_IMAGE:
+                        guess = onRight.name
+
+            if not guess:
+                # "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 labelish, that text might be the label. Given both
+                # text above and below the entry, the most likely label is
+                # probably the text that is vertically nearest the entry.
+                # This theory will, of course, require testing "in the wild."
+                #
+                containingCell = \
+                        self.getContainingRole(obj, rolenames.ROLE_TABLE_CELL)
+                if containingCell:
+                    parent = containingCell.parent
+                    [row, col] = self.getCellCoordinates(containingCell)
+                    aboveText = None
+                    cellAboveIsField = False
+                    if row > 0:
+                        cell = parent.table.getAccessibleAt(row - 1, col)
+                        cellAbove = atspi.Accessible.makeAccessible(cell)
+                        [objAbove, offset] = \
+                                  self.findFirstCaretContext(cellAbove, 0)
+                        aboveExtents = self.getExtents(cellAbove, 0, 1)
+                        cellAboveIsField = self.isFormField(objAbove)
+                        if cellAbove.text and not cellAboveIsField:
+                            aboveText = self.getDisplayedText(cellAbove)
+
+                    belowText = None
+                    cellBelowIsField = False
+                    rowspan = parent.table.getRowExtentAt(row, col)
+                    nextRow = row + rowspan
+                    if nextRow <= parent.table.nRows - 1:
+                        cell = parent.table.getAccessibleAt(nextRow, col)
+                        cellBelow = atspi.Accessible.makeAccessible(cell)
+                        [objBelow, offset] = \
+                                  self.findFirstCaretContext(cellBelow, 0)
+                        belowExtents = self.getExtents(cellBelow, 0, 1)
+                        cellBelowIsField = self.isFormField(objBelow)
+                        if cellBelow.text and not cellBelowIsField:
+                            belowText = self.getDisplayedText(cellBelow)
+
+                    if aboveText:
+                        if not belowText:
+                            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:
+                        guess = belowText
+
+                    elif cellAboveIsField:
+                        # 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 field above us is of the same type and
+                        # size (say, within 1 pixel).
+                        #
+                        grid = False
+                        if obj.role == objAbove.role:
+                            if abs(objExtents[2] - aboveExtents[2])<= 1 and \
+                               abs(objExtents[3] - aboveExtents[3]) <= 1:
+                                   grid = True
+                        if grid:
+                            cell = 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)
+
+            if not guess:
+                # Maybe the text is above us, but not in a table cell.
+                #
+                [prevObj, prevOffset] = self.findPreviousLine(obj, 0)
+                prevLineContents = self.getLineContentsAtOffset(prevObj,
+                                                                prevOffset)
+                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 entry 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]):
+                        # We only want to speak text up to the entry.
+                        #
+                        offset = -1
+                        if obj.parent == content[0]:
+                            offset = self.getCharacterOffsetInParent(obj)
+                        guess = self.getDisplayedText(content[0], offset)
+                        break
+
+            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.                       #
