Index: src/orca/scripts/apps/yelp.py
===================================================================
--- src/orca/scripts/apps/yelp.py	(revision 0)
+++ src/orca/scripts/apps/yelp.py	(revision 0)
@@ -0,0 +1,144 @@
+# Orca
+#
+# Copyright 2005-2008 Sun Microsystems Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA  02110-1301 USA.
+
+"""Custom script for Yelp."""
+
+__id__        = "$Id:$"
+__version__   = "$Revision:$"
+__date__      = "$Date:$"
+__copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc."
+__license__   = "LGPL"
+
+import pyatspi
+
+import orca.orca as orca
+import orca.orca_state as orca_state
+import orca.settings as settings
+import orca.speech as speech
+
+import orca.scripts.toolkits.Gecko as Gecko
+
+class Script(Gecko.Script):
+
+    def __init__(self, app):
+        Gecko.Script.__init__(self, app)
+
+        # By default, don't present if Yelp is not the active application.
+        #
+        self.presentIfInactive = False
+
+    def onDocumentLoadComplete(self, event):
+        """Called when a web page load is completed."""
+
+        # We can't count on this event, but often when the first Yelp
+        # window is opened, this event is emitted. If we silently set
+        # the locusOfFocus here, the frame won't be the locusOfFocus
+        # for the initial call to getDocumentFrame(). As an added
+        # bonus, when we detect this condition, we can place the user
+        # at the top of the content. Yelp seems to prefer the end,
+        # which is confusing non-visually.
+        #
+        if event.source.getRole() == pyatspi.ROLE_DOCUMENT_FRAME:
+            orca.setLocusOfFocus(event, event.source, False)
+
+        return Gecko.Script.onDocumentLoadComplete(self, event)
+
+    def getDocumentFrame(self):
+        """Returns the document frame that holds the content being shown."""
+
+        obj = orca_state.locusOfFocus
+        #print "getDocumentFrame", obj
+
+        if not obj:
+            return None
+
+        role = obj.getRole()
+        if role == pyatspi.ROLE_DOCUMENT_FRAME:
+            # We caught a lucky break.
+            #
+            return obj
+        elif role == pyatspi.ROLE_FRAME:
+            # The window was just activated. Do not look from the top down;
+            # it will cause the yelp hierarchy to become crazy, resulting in
+            # all future events having an empty name for the application.
+            # See bug 356041 for more information.
+            #
+            return None
+        else:
+            # We might be in some content. In this case, look up.
+            #
+            return self.getAncestor(obj,
+                                    [pyatspi.ROLE_DOCUMENT_FRAME,
+                                     pyatspi.ROLE_EMBEDDED],
+                                    [pyatspi.ROLE_FRAME])
+
+    def onCaretMoved(self, event):
+        """Called whenever the caret moves.
+
+        Arguments:
+        - event: the Event
+        """
+
+        # Unlike the unpredictable wild, wild web, odds are good that a
+        # caret-moved event from document content in Yelp is valid. But
+        # depending upon the locusOfFocus at the time this event is issued
+        # the default Gecko toolkit script might not do the right thing.
+        # Rather than risk breaking access to web content, we'll just set
+        # the locusOfFocus here before sending this event on.
+        #
+        if self.inDocumentContent(event.source):
+            orca.setLocusOfFocus(event, event.source)
+
+        return Gecko.Script.onCaretMoved(self, event)
+
+    def onChildrenChanged(self, event):
+        """Called when a child node has changed. When the user follows a
+        link in an open Yelp window, the document frame content changes,
+        but we don't get any events to tell us something has loaded.
+        STILL INVESTIGATING, but it seems like we get a reliable
+        object:children-changed:add event for detail1 == -1 for the
+        document frame. Let's go with that for now....
+        """
+
+        if event.type.startswith("object:children-changed:add") \
+           and event.detail1 == -1 and event.source \
+           and event.source.getRole() == pyatspi.ROLE_DOCUMENT_FRAME:
+
+            #print "Yelp object:children-changed:add", obj, characterOffset
+
+            # If the frame is the locusOfFocus, getCaretContext() will fail
+            # because we don't want to look from the top down to find the
+            # document frame and cause Yelp to have an identity crisis. So
+            # let's just set it here and avoid that whole mess.
+            #
+            orca.setLocusOfFocus(event, event.source, False)
+            [obj, characterOffset] = self.getCaretContext()
+            if obj:
+                self.setCaretPosition(obj, characterOffset)
+                if obj.getState().contains(pyatspi.STATE_FOCUSED):
+                    speech.speakUtterances(\
+                        self.speechGenerator.getSpeech(obj, False))
+                elif not Gecko.script_settings.sayAllOnLoad:
+                    self.speakContents(\
+                        self.getLineContentsAtOffset(obj, characterOffset))
+                elif settings.enableSpeech:
+                    self.sayAll(None)
+
+        else:
+            Gecko.Script.onChildrenChanged(self, event)
Index: src/orca/scripts/apps/Makefile.am
===================================================================
--- src/orca/scripts/apps/Makefile.am	(revision 4168)
+++ src/orca/scripts/apps/Makefile.am	(working copy)
@@ -27,7 +27,8 @@
 	metacity.py \
 	Mozilla.py \
 	nautilus.py \
-	notification-daemon.py
+	notification-daemon.py \
+	yelp.py
 
 orca_pythondir=$(pyexecdir)/orca/scripts/apps
 
Index: src/orca/scripts/toolkits/Gecko/script.py
===================================================================
--- src/orca/scripts/toolkits/Gecko/script.py	(revision 4168)
+++ src/orca/scripts/toolkits/Gecko/script.py	(working copy)
@@ -1079,7 +1079,8 @@
             if context.currentOffset == 0 and \
                context.obj.getRole() in [pyatspi.ROLE_HEADING,
                                          pyatspi.ROLE_SECTION,
-                                         pyatspi.ROLE_PARAGRAPH]:
+                                         pyatspi.ROLE_PARAGRAPH] \
+               and context.obj.parent.getRole() != pyatspi.ROLE_LINK:
                 characterCount = context.obj.queryText().characterCount
                 self.setCaretPosition(context.obj, characterCount-1)
         elif callbackType == speechserver.SayAllContext.INTERRUPTED:
@@ -3299,6 +3300,19 @@
         if obj is None or obj.getRole() == pyatspi.ROLE_HEADING:
             return -1
 
+        try:
+            state = obj.getState()
+        except:
+            return -1
+        else:
+            if state.contains(pyatspi.STATE_DEFUNCT):
+                # Yelp (or perhaps the work-in-progress a11y patch)
+                # seems to be guilty of this.
+                #
+                #print "getNodeLevel - obj is defunct", obj
+                debug.printException(debug.LEVEL_SEVERE)
+                return -1
+
         attrs = obj.getAttributes()
         if attrs is None:
             return -1
@@ -3464,12 +3478,24 @@
             #    2) It seems that down arrow moves us to the table, but up
             #       arrow moves us to the last row.  Possible side effect
             #       of our existing caret browsing implementation??]]]
-            #
-            if obj[0] and obj[0].getRole == pyatspi.ROLE_CAPTION:
+            #    3) Figure out why the heck the table of contents for at
+            #       least some Yelp content consists of a table whose sole
+            #       child is a list!!!
+            if obj[0] and obj[0].getRole() in [pyatspi.ROLE_CAPTION,
+                                               pyatspi.ROLE_LIST]:
                 obj = obj[0]
             else:
                 obj = obj.queryTable().getAccessibleAt(0, 0)
 
+            if not obj:
+                # Yelp (or perhaps the work-in-progress a11y patch) seems
+                # to be guilty of this. Although that may have been the
+                # table of contents thing (see #3 above).
+                #
+                #print "getObjectsFromEOCs - in Table, missing an accessible"
+                debug.printException(debug.LEVEL_SEVERE)
+                return []
+
         objects = []
         text = self.queryNonEmptyText(obj)
         if text:
@@ -4407,6 +4433,9 @@
         if self.isSameObject(obj, documentFrame):
             [obj, characterOffset] = self.getCaretContext()
 
+        if not obj:
+            return None
+
         index = obj.getIndexInParent() - 1
         if (index < 0):
             if not self.isSameObject(obj, documentFrame):
@@ -4502,6 +4531,9 @@
         if self.isSameObject(obj, documentFrame):
             [obj, characterOffset] = self.getCaretContext()
 
+        if not obj:
+            return None
+
         # If the object has children, we'll choose the first one,
         # unless it's a combo box or a focusable HTML list.
         #
@@ -4715,8 +4747,24 @@
                                           -1,
                                           includeNonText)
 
-        return self._documentFrameCaretContext[hash(documentFrame)]
+        [obj, caretOffset] = \
+            self._documentFrameCaretContext[hash(documentFrame)]
 
+        # Yelp is seemingly fond of killing children for sport. Better
+        # check for that.
+        #
+        try:
+            state = obj.getState()
+        except:
+            return [None, -1]
+        else:
+            if state.contains(pyatspi.STATE_DEFUNCT):
+                #print "getCaretContext: defunct object", obj
+                debug.printException(debug.LEVEL_SEVERE)
+                [obj, caretOffset] = [None, -1]
+
+        return [obj, caretOffset]
+
     def getCharacterAtOffset(self, obj, characterOffset):
         """Returns the character at the given characterOffset in the
         given object or None if the object does not implement the
@@ -5441,7 +5490,8 @@
 
         extents = self.getExtents(obj, characterOffset, characterOffset + 1)
         nextExtents = self.getExtents(nextObj, nextOffset, nextOffset + 1)
-        while self.onSameLine(extents, nextExtents):
+        while self.onSameLine(extents, nextExtents) \
+              and (extents != nextExtents):
             [nextObj, nextOffset] = \
                 self.findNextCaretInOrder(nextObj, nextOffset)
             nextExtents = self.getExtents(nextObj, nextOffset, nextOffset + 1)
Index: src/orca/settings.py
===================================================================
--- src/orca/settings.py	(revision 4168)
+++ src/orca/settings.py	(working copy)
@@ -975,11 +975,6 @@
 #
 setScriptMapping(re.compile(_('[Ee]volution')), "evolution")
 
-# Translators: see the regular expression note above.  This is for the
-# help application (yelp).
-#
-setScriptMapping(re.compile(_('yelp')), "Mozilla")
-
 # Translators: see the regular expression note above.  This is for a
 # version of Mozilla Firefox, which chooses to create strange names
 # for itself at the drop of a hat.
@@ -1024,6 +1019,13 @@
 #
 setScriptMapping(re.compile(_('gaim')), "pidgin")
 
+# Translators: see the regular expression note above.  This is for
+# supporting yelp, which sometimes identifies itself as gnome-help.
+# [[[TODO - JD: Not marked for translation due to string freeze.]]]
+#
+#setScriptMapping(re.compile(_('gnome-help')), "yelp")
+setScriptMapping(re.compile('gnome-help'), "yelp")
+
 # Show deprecated messeges in debug output.
 # Set this to True to help find potential pyatspi porting problems
 #
Index: src/orca/structural_navigation.py
===================================================================
--- src/orca/structural_navigation.py	(revision 4168)
+++ src/orca/structural_navigation.py	(working copy)
@@ -783,6 +783,20 @@
         """
 
         obj = obj or self.getCurrentObject()
+
+        # Yelp is seemingly fond of killing children for sport. Better
+        # check for that.
+        #
+        try:
+            state = obj.getState()
+        except:
+            return [None, False]
+        else:
+            if state.contains(pyatspi.STATE_DEFUNCT):
+                #print "goObject: defunct object", obj
+                debug.printException(debug.LEVEL_SEVERE)
+                return [None, False]
+
         success = False
         wrap = settings.wrappedStructuralNavigation
         # Try to find it using Collection first.  But don't do this with form
