[GTK] ATK text-caret-moved and text-selection-changed events not being emitted
https://bugs.webkit.org/show_bug.cgi?id=76069

Reviewed by Martin Robinson.

Source/WebCore:

Fix bug introduced with patch for Bug 72830.

* accessibility/AccessibilityObject.cpp:
(WebCore::AccessibilityObject::isDescendantOfObject): New function,
to check if an accessibility object is a descendant of other object.
(WebCore::AccessibilityObject::isAncestorOfObject): New function,
to check if an accessibility object is an ancestor of other object.
* accessibility/AccessibilityObject.h:

* accessibility/gtk/AccessibilityObjectWrapperAtk.cpp:
(webkit_accessible_text_get_caret_offset): Make sure to pass the
right reference object to objectFocusedAndCaretOffsetUnignored.
(objectFocusedAndCaretOffsetUnignored): Use positionBeforeNode
instead of firstPositionInNode for calculating the begining of the
range used to calculate the offsets. Ensure that the reference
object is never a descendant of the actual object being returned.

* editing/gtk/FrameSelectionGtk.cpp:
(WebCore::FrameSelection::notifyAccessibilityForSelectionChange):
Pass the right accessibility object associated with the current
selection to objectFocusedAndCaretOffsetUnignored.

Source/WebKit/gtk:

Update caret browsing related unit tests to check emissions of
'text-caret-moved' and 'text-selection-changed' signals.

* tests/testatk.c:
(textCaretMovedCallback): New callback for 'text-caret-moved'.
(testWebkitAtkCaretOffsets): Check emissions of 'text-caret-moved'.
(textSelectionChangedCallback): New callback for 'text-selection-changed'.
(testWebkitAtkTextSelections): Check emissions of 'text-selection-changed'.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@105590 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 7c89fe8..dfb424d 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,32 @@
+2012-01-22  Mario Sanchez Prada  <msanchez@igalia.com>
+
+        [GTK] ATK text-caret-moved and text-selection-changed events not being emitted
+        https://bugs.webkit.org/show_bug.cgi?id=76069
+
+        Reviewed by Martin Robinson.
+
+        Fix bug introduced with patch for Bug 72830.
+
+        * accessibility/AccessibilityObject.cpp:
+        (WebCore::AccessibilityObject::isDescendantOfObject): New function,
+        to check if an accessibility object is a descendant of other object.
+        (WebCore::AccessibilityObject::isAncestorOfObject): New function,
+        to check if an accessibility object is an ancestor of other object.
+        * accessibility/AccessibilityObject.h:
+
+        * accessibility/gtk/AccessibilityObjectWrapperAtk.cpp:
+        (webkit_accessible_text_get_caret_offset): Make sure to pass the
+        right reference object to objectFocusedAndCaretOffsetUnignored.
+        (objectFocusedAndCaretOffsetUnignored): Use positionBeforeNode
+        instead of firstPositionInNode for calculating the begining of the
+        range used to calculate the offsets. Ensure that the reference
+        object is never a descendant of the actual object being returned.
+
+        * editing/gtk/FrameSelectionGtk.cpp:
+        (WebCore::FrameSelection::notifyAccessibilityForSelectionChange):
+        Pass the right accessibility object associated with the current
+        selection to objectFocusedAndCaretOffsetUnignored.
+
 2012-01-21  David Reveman  <reveman@chromium.org>
 
         [Chromium] Incremental texture updates are not atomic.
diff --git a/Source/WebCore/accessibility/AccessibilityObject.cpp b/Source/WebCore/accessibility/AccessibilityObject.cpp
index 1b8cd98..bbbbff2 100644
--- a/Source/WebCore/accessibility/AccessibilityObject.cpp
+++ b/Source/WebCore/accessibility/AccessibilityObject.cpp
@@ -1294,6 +1294,26 @@
     return AccessibilityOrientationHorizontal;
 }    
 
+bool AccessibilityObject::isDescendantOfObject(const AccessibilityObject* axObject) const
+{
+    if (!axObject || !axObject->hasChildren())
+        return false;
+
+    for (const AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
+        if (parent == axObject)
+            return true;
+    }
+    return false;
+}
+
+bool AccessibilityObject::isAncestorOfObject(const AccessibilityObject* axObject) const
+{
+    if (!axObject)
+        return false;
+
+    return this == axObject || axObject->isDescendantOfObject(this);
+}
+
 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap;
 
 struct RoleEntry {
diff --git a/Source/WebCore/accessibility/AccessibilityObject.h b/Source/WebCore/accessibility/AccessibilityObject.h
index bd5741c..c417cc9 100644
--- a/Source/WebCore/accessibility/AccessibilityObject.h
+++ b/Source/WebCore/accessibility/AccessibilityObject.h
@@ -564,6 +564,8 @@
     virtual AccessibilityObject* activeDescendant() const { return 0; }    
     virtual void handleActiveDescendantChanged() { }
     virtual void handleAriaExpandedChanged() { }
+    bool isDescendantOfObject(const AccessibilityObject*) const;
+    bool isAncestorOfObject(const AccessibilityObject*) const;
     
     static AccessibilityRole ariaRoleToWebCoreRole(const String&);
     const AtomicString& getAttribute(const QualifiedName&) const;
diff --git a/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp b/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp
index eadacaf..fe9f007 100644
--- a/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp
+++ b/Source/WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp
@@ -1345,6 +1345,12 @@
     if (!coreObject->isAccessibilityRenderObject())
         return 0;
 
+    // We need to make sure we pass a valid object as reference.
+    if (coreObject->accessibilityIsIgnored())
+        coreObject = coreObject->parentObjectUnignored();
+    if (!coreObject)
+        return 0;
+
     int offset;
     if (!objectFocusedAndCaretOffsetUnignored(coreObject, offset))
         return 0;
@@ -2736,19 +2742,25 @@
         return 0;
 
     // Look for the actual (not ignoring accessibility) selected object.
-    if (focusedObject->accessibilityIsIgnored())
-        focusedObject = focusedObject->parentObjectUnignored();
-    if (!focusedObject)
+    AccessibilityObject* firstUnignoredParent = focusedObject;
+    if (firstUnignoredParent->accessibilityIsIgnored())
+        firstUnignoredParent = firstUnignoredParent->parentObjectUnignored();
+    if (!firstUnignoredParent)
         return 0;
 
     // Don't ignore links if the offset is being requested for a link.
-    if (!referenceObject->isLink() && focusedObject->isLink())
-        focusedObject = focusedObject->parentObjectUnignored();
-    if (!focusedObject)
+    if (!referenceObject->isLink() && firstUnignoredParent->isLink())
+        firstUnignoredParent = firstUnignoredParent->parentObjectUnignored();
+    if (!firstUnignoredParent)
         return 0;
 
+    // The reference object must either coincide with the focused
+    // object being considered, or be a descendant of it.
+    if (referenceObject->isDescendantOfObject(firstUnignoredParent))
+        referenceObject = firstUnignoredParent;
+
     Node* startNode = 0;
-    if (focusedObject != referenceObject || focusedObject->isTextControl()) {
+    if (firstUnignoredParent != referenceObject || firstUnignoredParent->isTextControl()) {
         // We need to use the first child's node of the reference
         // object as the start point to calculate the caret offset
         // because we want it to be relative to the object of
@@ -2759,10 +2771,10 @@
             startNode = axFirstChild->node();
     }
     if (!startNode)
-        startNode = focusedObject->node();
+        startNode = firstUnignoredParent->node();
 
-    VisiblePosition startPosition = VisiblePosition(firstPositionInNode(startNode), DOWNSTREAM);
-    VisiblePosition endPosition = focusedObject->selection().visibleEnd();
+    VisiblePosition startPosition = VisiblePosition(positionBeforeNode(startNode), DOWNSTREAM);
+    VisiblePosition endPosition = firstUnignoredParent->selection().visibleEnd();
 
     if (startPosition == endPosition)
         offset = 0;
@@ -2774,7 +2786,7 @@
         offset = TextIterator::rangeLength(range.get(), true);
     }
 
-    return focusedObject;
+    return firstUnignoredParent;
 }
 
 #endif // HAVE(ACCESSIBILITY)
diff --git a/Source/WebCore/editing/gtk/FrameSelectionGtk.cpp b/Source/WebCore/editing/gtk/FrameSelectionGtk.cpp
index a637870..af14d39 100644
--- a/Source/WebCore/editing/gtk/FrameSelectionGtk.cpp
+++ b/Source/WebCore/editing/gtk/FrameSelectionGtk.cpp
@@ -84,8 +84,10 @@
     if (!m_selection.start().isNotNull() || !m_selection.end().isNotNull())
         return;
 
-    // Look for the accessibility object for the Frame.
-    AccessibilityObject* accessibilityObject = m_frame->document()->axObjectCache()->rootObjectForFrame(m_frame);
+    RenderObject* focusedNode = m_selection.end().containerNode()->renderer();
+    AccessibilityObject* accessibilityObject = m_frame->document()->axObjectCache()->getOrCreate(focusedNode);
+
+    // Need to check this as getOrCreate could return 0.
     if (!accessibilityObject)
         return;
 
diff --git a/Source/WebKit/gtk/ChangeLog b/Source/WebKit/gtk/ChangeLog
index 08bb849..c70e10a 100644
--- a/Source/WebKit/gtk/ChangeLog
+++ b/Source/WebKit/gtk/ChangeLog
@@ -1,3 +1,19 @@
+2012-01-22  Mario Sanchez Prada  <msanchez@igalia.com>
+
+        [GTK] ATK text-caret-moved and text-selection-changed events not being emitted
+        https://bugs.webkit.org/show_bug.cgi?id=76069
+
+        Reviewed by Martin Robinson.
+
+        Update caret browsing related unit tests to check emissions of
+        'text-caret-moved' and 'text-selection-changed' signals.
+
+        * tests/testatk.c:
+        (textCaretMovedCallback): New callback for 'text-caret-moved'.
+        (testWebkitAtkCaretOffsets): Check emissions of 'text-caret-moved'.
+        (textSelectionChangedCallback): New callback for 'text-selection-changed'.
+        (testWebkitAtkTextSelections): Check emissions of 'text-selection-changed'.
+
 2012-01-18  Evan Nemerson  <evan@coeus-group.com>
 
         [GTK] WebKit-3.0.gir does not include information about C includes or exported packages
diff --git a/Source/WebKit/gtk/tests/testatk.c b/Source/WebKit/gtk/tests/testatk.c
index a11552f..b48b557 100644
--- a/Source/WebKit/gtk/tests/testatk.c
+++ b/Source/WebKit/gtk/tests/testatk.c
@@ -254,6 +254,17 @@
     }
 }
 
+static gchar* textCaretMovedResult = 0;
+
+static void textCaretMovedCallback(AtkText* text, gint pos, gpointer data)
+{
+    g_assert(ATK_IS_TEXT(text));
+
+    g_free(textCaretMovedResult);
+    AtkRole role = atk_object_get_role(ATK_OBJECT(text));
+    textCaretMovedResult = g_strdup_printf("|%s|%d|", atk_role_get_name(role), pos);
+}
+
 static void testWebkitAtkCaretOffsets()
 {
     WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
@@ -267,6 +278,8 @@
 
     AtkObject* header = atk_object_ref_accessible_child(object, 0);
     g_assert(ATK_IS_TEXT(header));
+    g_signal_connect(header, "text-caret-moved", G_CALLBACK(textCaretMovedCallback), 0);
+
     gchar* text = atk_text_get_text(ATK_TEXT(header), 0, -1);
     g_assert_cmpstr(text, ==, "A text header");
     g_free (text);
@@ -276,9 +289,12 @@
     g_assert_cmpint(result, ==, TRUE);
     gint offset = atk_text_get_caret_offset(ATK_TEXT(header));
     g_assert_cmpint(offset, ==, 5);
+    g_assert_cmpstr(textCaretMovedResult, ==, "|heading|5|");
 
     AtkObject* paragraph = atk_object_ref_accessible_child(object, 1);
     g_assert(ATK_IS_TEXT(paragraph));
+    g_signal_connect(paragraph, "text-caret-moved", G_CALLBACK(textCaretMovedCallback), 0);
+
     text = atk_text_get_text(ATK_TEXT(paragraph), 0, -1);
     g_assert_cmpstr(text, ==, "A paragraph with a link in the middle");
     g_free (text);
@@ -288,16 +304,32 @@
     g_assert_cmpint(result, ==, TRUE);
     offset = atk_text_get_caret_offset(ATK_TEXT(paragraph));
     g_assert_cmpint(offset, ==, 5);
+    g_assert_cmpstr(textCaretMovedResult, ==, "|paragraph|5|");
 
     result = atk_text_set_caret_offset(ATK_TEXT(paragraph), 20);
     g_assert_cmpint(result, ==, TRUE);
     offset = atk_text_get_caret_offset(ATK_TEXT(paragraph));
     g_assert_cmpint(offset, ==, 20);
+    g_assert_cmpstr(textCaretMovedResult, ==, "|paragraph|20|");
 
     result = atk_text_set_caret_offset(ATK_TEXT(paragraph), 30);
     g_assert_cmpint(result, ==, TRUE);
     offset = atk_text_get_caret_offset(ATK_TEXT(paragraph));
     g_assert_cmpint(offset, ==, 30);
+    g_assert_cmpstr(textCaretMovedResult, ==, "|paragraph|30|");
+
+    AtkObject* link = atk_object_ref_accessible_child(paragraph, 0);
+    g_assert(ATK_IS_TEXT(link));
+    text = atk_text_get_text(ATK_TEXT(link), 0, -1);
+    g_assert_cmpstr(text, ==, "with a link");
+    g_free (text);
+
+    result = atk_text_set_caret_offset(ATK_TEXT(link), 5);
+    g_assert_cmpint(result, ==, TRUE);
+    offset = atk_text_get_caret_offset(ATK_TEXT(link));
+    g_assert_cmpint(offset, ==, 5);
+    /* Positions inside links are reported relative to the paragraph. */
+    g_assert_cmpstr(textCaretMovedResult, ==, "|paragraph|17|");
 
     AtkObject* list = atk_object_ref_accessible_child(object, 2);
     g_assert(ATK_OBJECT(list));
@@ -355,8 +387,11 @@
     offset = atk_text_get_caret_offset(ATK_TEXT(textEntry));
     g_assert_cmpint(offset, ==, 5);
 
+    g_free(textCaretMovedResult);
+
     g_object_unref(header);
     g_object_unref(paragraph);
+    g_object_unref(link);
     g_object_unref(list);
     g_object_unref(listItem);
     g_object_unref(panel);
@@ -1087,6 +1122,20 @@
     atk_attribute_set_free(set3);
 }
 
+static gchar* textSelectionChangedResult = 0;
+
+static void textSelectionChangedCallback(AtkText* text, gpointer data)
+{
+    g_assert(ATK_IS_TEXT(text));
+
+    g_free(textSelectionChangedResult);
+    AtkRole role = atk_object_get_role(ATK_OBJECT(text));
+    int startOffset = 0;
+    int endOffset = 0;
+    atk_text_get_selection(ATK_TEXT(text), 0, &startOffset, &endOffset);
+    textSelectionChangedResult = g_strdup_printf("|%s|%d|%d|", atk_role_get_name(role), startOffset, endOffset);
+}
+
 static void testWebkitAtkTextSelections()
 {
     WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
@@ -1100,9 +1149,11 @@
 
     AtkText* paragraph1 = ATK_TEXT(atk_object_ref_accessible_child(object, 0));
     g_assert(ATK_IS_TEXT(paragraph1));
+    g_signal_connect(paragraph1, "text-selection-changed", G_CALLBACK(textSelectionChangedCallback), 0);
 
     AtkText* paragraph2 = ATK_TEXT(atk_object_ref_accessible_child(object, 1));
     g_assert(ATK_IS_TEXT(paragraph2));
+    g_signal_connect(paragraph2, "text-selection-changed", G_CALLBACK(textSelectionChangedCallback), 0);
 
     AtkText* link = ATK_TEXT(atk_object_ref_accessible_child(ATK_OBJECT(paragraph2), 0));
     g_assert(ATK_IS_TEXT(link));
@@ -1138,11 +1189,13 @@
     result = atk_text_set_selection(paragraph1, 0, 5, 25);
     g_assert(result);
     g_assert_cmpint(atk_text_get_n_selections(paragraph1), ==, 1);
+    g_assert_cmpstr(textSelectionChangedResult, ==, "|paragraph|5|25|");
     selectedText = atk_text_get_selection(paragraph1, 0, &startOffset, &endOffset);
     g_assert_cmpint(startOffset, ==, 5);
     g_assert_cmpint(endOffset, ==, 25);
     g_assert_cmpstr(selectedText, ==, "agraph with plain te");
     g_free (selectedText);
+
     /* Try removing the selection from other AtkText object (should fail). */
     result = atk_text_remove_selection(paragraph2, 0);
     g_assert(!result);
@@ -1185,6 +1238,7 @@
     result = atk_text_set_selection(paragraph2, 0, 27, 37);
     g_assert(result);
     g_assert_cmpint(atk_text_get_n_selections(paragraph2), ==, 1);
+    g_assert_cmpstr(textSelectionChangedResult, ==, "|paragraph|27|37|");
     selectedText = atk_text_get_selection(paragraph2, 0, &startOffset, &endOffset);
     g_assert_cmpint(startOffset, ==, 27);
     g_assert_cmpint(endOffset, ==, 37);
@@ -1234,6 +1288,8 @@
     g_assert_cmpstr(selectedText, ==, "A list");
     g_free (selectedText);
 
+    g_free(textSelectionChangedResult);
+
     g_object_unref(paragraph1);
     g_object_unref(paragraph2);
     g_object_unref(list);