2010-11-30 Mario Sanchez Prada <msanchez@igalia.com>
authormario@webkit.org <mario@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 30 Nov 2010 21:16:14 +0000 (21:16 +0000)
committermario@webkit.org <mario@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 30 Nov 2010 21:16:14 +0000 (21:16 +0000)
        Reviewed by Chris Fleizach.

        [GTK] Implement ROLE_COMBO_BOX
        https://bugs.webkit.org/show_bug.cgi?id=25678

        Implement the remaining bits for combo boxes.

        This patch finishes the implementation, from the point of view of
        the Atk library, of those objects exposed to ATK as combo boxes,
        and their related elements (menus and menu items). It therefore
        implements the proper interfaces for each type of object related
        to combo boxes (AtkSelection for the combo box, AtkText for every
        menu item and AtkAction for the combo box, the menu and the menu
        items), takes care of emitting the proper signals when focus or a
        given a selection changes and adds a new unit test to check all
        this new stuff.

        Make possible to ask an AccessibleMenuListOption for a sensible
        string representation, so far only available through the private
        and MSAA related method nameForMSAA. Just moved the implementation
        of that method to an overriden version of stringValue(), which is
        platform independent, and called that from nameForMSAA().

        * accessibility/AccessibilityMenuListOption.h:
        * accessibility/AccessibilityMenuListOption.cpp:
        (WebCore::AccessibilityMenuListOption::nameForMSAA): Just call to
        stringValue(), which holds from now on that used to be here.
        (WebCore::AccessibilityMenuListOption::stringValue): New, override
        of AccessibilityObject::stringValue() to return a proper string.

        Emit the missing signals when a selection is made.

        * accessibility/gtk/AXObjectCacheAtk.cpp:
        (WebCore::AXObjectCache::postPlatformNotification): Emit the usual
        'focus' signals when a selection is made over the combo box.

        * accessibility/gtk/AccessibilityObjectWrapperAtk.cpp:
        (setAtkStateSetFromCoreObject): Ensure the EXPANDABLE and EXPANDED
        Atk states are added to the state set when needed.
        (listObjectForSelection): New, returns the proper list object (the
        one holding the list of available options as its children) for an
        specific AtkObject implementing AtkSelection. This is needed
        because sometimes the selectable options are not directly children
        of the AtkSelection object (i.e. a combo box has a 'menu' object
        as its only child of it, holding the list of options as children).
        (optionFromList): Use listObjectForSelection() to get the actual
        object holding the list of children as the available options.
        (optionFromSelection): Add support for combo boxes.
        (webkit_accessible_selection_add_selection): Ditto.
        (webkit_accessible_selection_clear_selection): Ditto.
        (webkit_accessible_selection_get_selection_count): Ditto.
        (webkit_accessible_selection_is_child_selected): Ditto.
        (webkit_accessible_selection_remove_selection): Ditto.
        (webkit_accessible_text_get_text): Makes sure stringValue() is
        considered to get the result substring when it was already
        considered when checking the maximum text length for the object.
        (getInterfaceMaskFromObject): Make sure the AtkSelection interface
        is implemented for the combo boxes, that the AtkText is
        implemented for the menu items and that the AtkAction interface is
        now implemented for every object (WebCore will decide what to do).

        Avoid a segfault crash when using this from unit tests.

        * platform/gtk/PopupMenuGtk.cpp:
        (WebCore::PopupMenuGtk::show): Make sure we got a valid GdkWindow
        before calling gdk_window_get_origin() over it.
2010-11-30  Mario Sanchez Prada  <msanchez@igalia.com>

        Reviewed by Chris Fleizach.

        [GTK] Implement ROLE_COMBO_BOX
        https://bugs.webkit.org/show_bug.cgi?id=25678

        New test to check the implementation of the combo boxes.

        * tests/testatk.c:
        (testWebkitAtkComboBox): New test, checking that the roles and the
        implemented interfaces for a combo box and its descendants work.
        (main): Added the new unit test.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@72958 268f45cc-cd09-0410-ab3c-d52691b4dbfc

WebCore/ChangeLog
WebCore/accessibility/AccessibilityMenuListOption.cpp
WebCore/accessibility/AccessibilityMenuListOption.h
WebCore/accessibility/gtk/AXObjectCacheAtk.cpp
WebCore/accessibility/gtk/AccessibilityObjectWrapperAtk.cpp
WebCore/platform/gtk/PopupMenuGtk.cpp
WebKit/gtk/ChangeLog
WebKit/gtk/tests/testatk.c

index f566ca7..d012553 100644 (file)
@@ -1,3 +1,72 @@
+2010-11-30  Mario Sanchez Prada  <msanchez@igalia.com>
+
+        Reviewed by Chris Fleizach.
+
+        [GTK] Implement ROLE_COMBO_BOX
+        https://bugs.webkit.org/show_bug.cgi?id=25678
+
+        Implement the remaining bits for combo boxes.
+
+        This patch finishes the implementation, from the point of view of
+        the Atk library, of those objects exposed to ATK as combo boxes,
+        and their related elements (menus and menu items). It therefore
+        implements the proper interfaces for each type of object related
+        to combo boxes (AtkSelection for the combo box, AtkText for every
+        menu item and AtkAction for the combo box, the menu and the menu
+        items), takes care of emitting the proper signals when focus or a
+        given a selection changes and adds a new unit test to check all
+        this new stuff.
+
+        Make possible to ask an AccessibleMenuListOption for a sensible
+        string representation, so far only available through the private
+        and MSAA related method nameForMSAA. Just moved the implementation
+        of that method to an overriden version of stringValue(), which is
+        platform independent, and called that from nameForMSAA().
+
+        * accessibility/AccessibilityMenuListOption.h:
+        * accessibility/AccessibilityMenuListOption.cpp:
+        (WebCore::AccessibilityMenuListOption::nameForMSAA): Just call to
+        stringValue(), which holds from now on that used to be here.
+        (WebCore::AccessibilityMenuListOption::stringValue): New, override
+        of AccessibilityObject::stringValue() to return a proper string.
+
+        Emit the missing signals when a selection is made.
+
+        * accessibility/gtk/AXObjectCacheAtk.cpp:
+        (WebCore::AXObjectCache::postPlatformNotification): Emit the usual
+        'focus' signals when a selection is made over the combo box.
+
+        * accessibility/gtk/AccessibilityObjectWrapperAtk.cpp:
+        (setAtkStateSetFromCoreObject): Ensure the EXPANDABLE and EXPANDED
+        Atk states are added to the state set when needed.
+        (listObjectForSelection): New, returns the proper list object (the
+        one holding the list of available options as its children) for an
+        specific AtkObject implementing AtkSelection. This is needed
+        because sometimes the selectable options are not directly children
+        of the AtkSelection object (i.e. a combo box has a 'menu' object
+        as its only child of it, holding the list of options as children).
+        (optionFromList): Use listObjectForSelection() to get the actual
+        object holding the list of children as the available options.
+        (optionFromSelection): Add support for combo boxes.
+        (webkit_accessible_selection_add_selection): Ditto.
+        (webkit_accessible_selection_clear_selection): Ditto.
+        (webkit_accessible_selection_get_selection_count): Ditto.
+        (webkit_accessible_selection_is_child_selected): Ditto.
+        (webkit_accessible_selection_remove_selection): Ditto.
+        (webkit_accessible_text_get_text): Makes sure stringValue() is
+        considered to get the result substring when it was already
+        considered when checking the maximum text length for the object.
+        (getInterfaceMaskFromObject): Make sure the AtkSelection interface
+        is implemented for the combo boxes, that the AtkText is
+        implemented for the menu items and that the AtkAction interface is
+        now implemented for every object (WebCore will decide what to do).
+
+        Avoid a segfault crash when using this from unit tests.
+
+        * platform/gtk/PopupMenuGtk.cpp:
+        (WebCore::PopupMenuGtk::show): Make sure we got a valid GdkWindow
+        before calling gdk_window_get_origin() over it.
+
 2010-11-30  Andreas Kling  <kling@webkit.org>
 
         Reviewed by Tor Arne Vestbø.
index d7473de..5bca580 100644 (file)
@@ -91,7 +91,7 @@ void AccessibilityMenuListOption::setSelected(bool b)
 
 String AccessibilityMenuListOption::nameForMSAA() const
 {
-    return static_cast<HTMLOptionElement*>(m_element.get())->text();
+    return stringValue();
 }
 
 bool AccessibilityMenuListOption::canSetSelectedAttribute() const
@@ -110,4 +110,9 @@ IntRect AccessibilityMenuListOption::elementRect() const
     return grandparent->elementRect();
 }
 
+String AccessibilityMenuListOption::stringValue() const
+{
+    return static_cast<HTMLOptionElement*>(m_element.get())->text();
+}
+
 } // namespace WebCore
index 7e27888..9393d56 100644 (file)
@@ -59,6 +59,7 @@ private:
     virtual void setSelected(bool);
     virtual bool canSetSelectedAttribute() const;
     virtual IntRect elementRect() const;
+    virtual String stringValue() const;
 
     RefPtr<HTMLElement> m_element;
     AccessibilityMenuListPopup* m_popup;
index 4216be4..c341a2d 100644 (file)
@@ -95,10 +95,19 @@ static void notifyChildrenSelectionChange(AccessibilityObject* object)
 
 void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AXNotification notification)
 {
+    AtkObject* axObject = coreObject->wrapper();
+    if (!axObject)
+        return;
+
     if (notification == AXCheckedStateChanged) {
         if (!coreObject->isCheckboxOrRadio())
             return;
-        g_signal_emit_by_name(coreObject->wrapper(), "state-change", "checked", coreObject->isChecked());
+        g_signal_emit_by_name(axObject, "state-change", "checked", coreObject->isChecked());
+    } else if (notification == AXMenuListValueChanged) {
+        if (!coreObject->isMenuList())
+            return;
+        g_signal_emit_by_name(axObject, "focus-event", true);
+        g_signal_emit_by_name(axObject, "state-change", "focused", true);
     } else if (notification == AXSelectedChildrenChanged)
         notifyChildrenSelectionChange(coreObject);
 }
index 3d4345a..ca0d80b 100644 (file)
@@ -58,6 +58,7 @@
 #include "RenderListItem.h"
 #include "RenderListMarker.h"
 #include "RenderText.h"
+#include "SelectElement.h"
 #include "TextEncoding.h"
 #include "TextIterator.h"
 #include "WebKitAccessibleHyperlink.h"
@@ -500,6 +501,12 @@ static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkSta
         atk_state_set_add_state(stateSet, ATK_STATE_SENSITIVE);
     }
 
+    if (coreObject->canSetExpandedAttribute())
+        atk_state_set_add_state(stateSet, ATK_STATE_EXPANDABLE);
+
+    if (coreObject->isExpanded())
+        atk_state_set_add_state(stateSet, ATK_STATE_EXPANDED);
+
     if (coreObject->canSetFocusAttribute())
         atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
 
@@ -694,13 +701,44 @@ static void atk_action_interface_init(AtkActionIface* iface)
 
 // Selection (for controls)
 
+static AccessibilityObject* listObjectForSelection(AtkSelection* selection)
+{
+    AccessibilityObject* coreSelection = core(selection);
+
+    // Only list boxes and menu lists supported so far.
+    if (!coreSelection->isListBox() && !coreSelection->isMenuList())
+        return 0;
+
+    // For list boxes the list object is just itself.
+    if (coreSelection->isListBox())
+        return coreSelection;
+
+    // For menu lists we need to return the first accessible child,
+    // with role MenuListPopupRole, since that's the one holding the list
+    // of items with role MenuListOptionRole.
+    AccessibilityObject::AccessibilityChildrenVector children = coreSelection->children();
+    if (!children.size())
+        return 0;
+
+    AccessibilityObject* listObject = children.at(0).get();
+    if (!listObject->isMenuListPopup())
+        return 0;
+
+    return listObject;
+}
+
 static AccessibilityObject* optionFromList(AtkSelection* selection, gint i)
 {
     AccessibilityObject* coreSelection = core(selection);
     if (!coreSelection || i < 0)
         return 0;
 
-    AccessibilityRenderObject::AccessibilityChildrenVector options = core(selection)->children();
+    // Need to select the proper list object depending on the type.
+    AccessibilityObject* listObject = listObjectForSelection(selection);
+    if (!listObject)
+        return 0;
+
+    AccessibilityRenderObject::AccessibilityChildrenVector options = listObject->children();
     if (i < static_cast<gint>(options.size()))
         return options.at(i).get();
 
@@ -712,14 +750,26 @@ static AccessibilityObject* optionFromSelection(AtkSelection* selection, gint i)
     // i is the ith selection as opposed to the ith child.
 
     AccessibilityObject* coreSelection = core(selection);
-    if (!coreSelection || i < 0)
+    if (!coreSelection || !coreSelection->isAccessibilityRenderObject() || i < 0)
         return 0;
 
     AccessibilityRenderObject::AccessibilityChildrenVector selectedItems;
     if (coreSelection->isListBox())
-        static_cast<AccessibilityListBox*>(coreSelection)->selectedChildren(selectedItems);
+        coreSelection->selectedChildren(selectedItems);
+    else if (coreSelection->isMenuList()) {
+        RenderObject* renderer = toAccessibilityRenderObject(coreSelection)->renderer();
+        if (!renderer)
+            return 0;
+
+        SelectElement* selectNode = toSelectElement(static_cast<Element*>(renderer->node()));
+        int selectedIndex = selectNode->selectedIndex();
+        const Vector<Element*> listItems = selectNode->listItems();
 
-    // TODO: Combo boxes
+        if (selectedIndex < 0 || selectedIndex >= static_cast<int>(listItems.size()))
+            return 0;
+
+        return optionFromList(selection, selectedIndex);
+    }
 
     if (i < static_cast<gint>(selectedItems.size()))
         return selectedItems.at(i).get();
@@ -729,11 +779,14 @@ static AccessibilityObject* optionFromSelection(AtkSelection* selection, gint i)
 
 static gboolean webkit_accessible_selection_add_selection(AtkSelection* selection, gint i)
 {
+    AccessibilityObject* coreSelection = core(selection);
+    if (!coreSelection)
+        return false;
+
     AccessibilityObject* option = optionFromList(selection, i);
-    if (option && core(selection)->isListBox()) {
-        AccessibilityListBoxOption* listBoxOption = static_cast<AccessibilityListBoxOption*>(option);
-        listBoxOption->setSelected(true);
-        return listBoxOption->isSelected();
+    if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) {
+        option->setSelected(true);
+        return option->isSelected();
     }
 
     return false;
@@ -746,7 +799,7 @@ static gboolean webkit_accessible_selection_clear_selection(AtkSelection* select
         return false;
 
     AccessibilityRenderObject::AccessibilityChildrenVector selectedItems;
-    if (coreSelection->isListBox()) {
+    if (coreSelection->isListBox() || coreSelection->isMenuList()) {
         // Set the list of selected items to an empty list; then verify that it worked.
         AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection);
         listBox->setSelectedChildren(selectedItems);
@@ -771,32 +824,54 @@ static AtkObject* webkit_accessible_selection_ref_selection(AtkSelection* select
 static gint webkit_accessible_selection_get_selection_count(AtkSelection* selection)
 {
     AccessibilityObject* coreSelection = core(selection);
-    if (coreSelection && coreSelection->isListBox()) {
+    if (!coreSelection || !coreSelection->isAccessibilityRenderObject())
+        return 0;
+
+    if (coreSelection->isListBox()) {
         AccessibilityRenderObject::AccessibilityChildrenVector selectedItems;
-        static_cast<AccessibilityListBox*>(coreSelection)->selectedChildren(selectedItems);
+        coreSelection->selectedChildren(selectedItems);
         return static_cast<gint>(selectedItems.size());
     }
 
+    if (coreSelection->isMenuList()) {
+        RenderObject* renderer = toAccessibilityRenderObject(coreSelection)->renderer();
+        if (!renderer)
+            return 0;
+
+        SelectElement* selectNode = toSelectElement(static_cast<Element*>(renderer->node()));
+        int selectedIndex = selectNode->selectedIndex();
+        const Vector<Element*> listItems = selectNode->listItems();
+
+        return selectedIndex >= 0 && selectedIndex < static_cast<int>(listItems.size());
+    }
+
     return 0;
 }
 
 static gboolean webkit_accessible_selection_is_child_selected(AtkSelection* selection, gint i)
 {
+    AccessibilityObject* coreSelection = core(selection);
+    if (!coreSelection)
+        return 0;
+
     AccessibilityObject* option = optionFromList(selection, i);
-    if (option && core(selection)->isListBox())
-        return static_cast<AccessibilityListBoxOption*>(option)->isSelected();
+    if (option && (coreSelection->isListBox() || coreSelection->isMenuList()))
+        return option->isSelected();
 
     return false;
 }
 
 static gboolean webkit_accessible_selection_remove_selection(AtkSelection* selection, gint i)
 {
+    AccessibilityObject* coreSelection = core(selection);
+    if (!coreSelection)
+        return 0;
+
     // TODO: This is only getting called if i == 0. What is preventing the rest?
     AccessibilityObject* option = optionFromSelection(selection, i);
-    if (option && core(selection)->isListBox()) {
-        AccessibilityListBoxOption* listBoxOption = static_cast<AccessibilityListBoxOption*>(option);
-        listBoxOption->setSelected(false);
-        return !listBoxOption->isSelected();
+    if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) {
+        option->setSelected(false);
+        return !option->isSelected();
     }
 
     return false;
@@ -969,8 +1044,11 @@ static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, g
 
     if (coreObject->isTextControl())
         ret = coreObject->doAXStringForRange(PlainTextRange(start, length));
-    else
-        ret = coreObject->textUnderElement().substring(start, length);
+    else {
+        ret = coreObject->stringValue().substring(start, length);
+        if (!ret)
+            ret = coreObject->textUnderElement().substring(start, length);
+    }
 
     if (!ret.length()) {
         // This can happen at least with anonymous RenderBlocks (e.g. body text amongst paragraphs)
@@ -2177,19 +2255,23 @@ static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject)
     AccessibilityRole role = coreObject->roleValue();
 
     // Action
-    if (!coreObject->actionVerb().isEmpty()) {
-        interfaceMask |= 1 << WAI_ACTION;
+    // As the implementation of the AtkAction interface is a very
+    // basic one (just relays in executing the default action for each
+    // object, and only supports having one action per object), it is
+    // better just to implement this interface for every instance of
+    // the WebKitAccessible class and let WebCore decide what to do.
+    interfaceMask |= 1 << WAI_ACTION;
 
-        if (!coreObject->accessibilityIsIgnored() && coreObject->isLink())
-            interfaceMask |= 1 << WAI_HYPERLINK;
-    }
+    // Hyperlink
+    if (coreObject->isLink())
+        interfaceMask |= 1 << WAI_HYPERLINK;
 
     // Selection
-    if (coreObject->isListBox())
+    if (coreObject->isListBox() || coreObject->isMenuList())
         interfaceMask |= 1 << WAI_SELECTION;
 
     // Text & Editable Text
-    if (role == StaticTextRole)
+    if (role == StaticTextRole || coreObject->isMenuListOption())
         interfaceMask |= 1 << WAI_TEXT;
     else if (coreObject->isAccessibilityRenderObject()) {
         if (coreObject->isTextControl()) {
index e7ff78e..07ca912 100644 (file)
@@ -58,8 +58,11 @@ void PopupMenuGtk::show(const IntRect& rect, FrameView* view, int index)
     } else
         gtk_container_foreach(GTK_CONTAINER(m_popup.get()), reinterpret_cast<GtkCallback>(menuRemoveItem), this);
 
-    int x, y;
-    gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(view->hostWindow()->platformPageClient())), &x, &y);
+    int x = 0;
+    int y = 0;
+    GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(view->hostWindow()->platformPageClient()));
+    if (window)
+        gdk_window_get_origin(window, &x, &y);
     m_menuPosition = view->contentsToWindow(rect.location());
     m_menuPosition = IntPoint(m_menuPosition.x() + x, m_menuPosition.y() + y + rect.height());
     m_indexMap.clear();
index 067f511..62af57f 100644 (file)
@@ -1,3 +1,17 @@
+2010-11-30  Mario Sanchez Prada  <msanchez@igalia.com>
+
+        Reviewed by Chris Fleizach.
+
+        [GTK] Implement ROLE_COMBO_BOX
+        https://bugs.webkit.org/show_bug.cgi?id=25678
+
+        New test to check the implementation of the combo boxes.
+
+        * tests/testatk.c:
+        (testWebkitAtkComboBox): New test, checking that the roles and the
+        implemented interfaces for a combo box and its descendants work.
+        (main): Added the new unit test.
+
 2010-11-30  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         Reviewed by Martin Robinson.
index f098f62..abd6091 100644 (file)
@@ -44,6 +44,8 @@ static const char* contentsInTable = "<html><body><table><tr><td>foo</td><td>bar
 
 static const char* contentsInTableWithHeaders = "<html><body><table><tr><th>foo</th><th>bar</th><th colspan='2'>baz</th></tr><tr><th>qux</th><td>1</td><td>2</td><td>3</td></tr><tr><th rowspan='2'>quux</th><td>4</td><td>5</td><td>6</td></tr><tr><td>6</td><td>7</td><td>8</td></tr><tr><th>corge</th><td>9</td><td>10</td><td>11</td></tr></table><table><tr><td>1</td><td>2</td></tr><tr><td>3</td><td>4</td></tr></table></body></html>";
 
+static const char* comboBoxSelector = "<html><body><select><option selected value='foo'>foo</option><option value='bar'>bar</option></select></body></html>";
+
 static const char* formWithTextInputs = "<html><body><form><input type='text' name='entry' /></form></body></html>";
 
 static const char* hypertextAndHyperlinks = "<html><body><p>A paragraph with no links at all</p><p><a href='http://foo.bar.baz/'>A line</a> with <a href='http://bar.baz.foo/'>a link in the middle</a> as well as at the beginning and <a href='http://baz.foo.bar/'>at the end</a></p></body></html>";
@@ -221,6 +223,107 @@ static void runGetTextTests(AtkText* textObject)
                         0, "This is a test. This is the second sentence. And this the third.", 0, 64);
 }
 
+static void testWebkitAtkComboBox()
+{
+    WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
+    g_object_ref_sink(webView);
+    GtkAllocation allocation = { 0, 0, 800, 600 };
+    gtk_widget_size_allocate(GTK_WIDGET(webView), &allocation);
+    webkit_web_view_load_string(webView, comboBoxSelector, 0, 0, 0);
+
+    /* Wait for the accessible objects to be created. */
+    waitForAccessibleObjects();
+
+    AtkObject* object = gtk_widget_get_accessible(GTK_WIDGET(webView));
+    g_assert(object);
+
+    AtkObject* formObject = atk_object_ref_accessible_child(object, 0);
+    g_assert(formObject);
+
+    AtkObject* comboBox = atk_object_ref_accessible_child(formObject, 0);
+    g_assert(ATK_IS_OBJECT(comboBox));
+
+    AtkObject* menuPopup = atk_object_ref_accessible_child(comboBox, 0);
+    g_assert(ATK_IS_OBJECT(menuPopup));
+
+    AtkObject* item1 = atk_object_ref_accessible_child(menuPopup, 0);
+    g_assert(ATK_IS_OBJECT(item1));
+
+    AtkObject* item2 = atk_object_ref_accessible_child(menuPopup, 1);
+    g_assert(ATK_IS_OBJECT(item2));
+
+    /* Check roles. */
+    g_assert(atk_object_get_role(comboBox) == ATK_ROLE_COMBO_BOX);
+    g_assert(atk_object_get_role(menuPopup) == ATK_ROLE_MENU);
+    g_assert(atk_object_get_role(item1) == ATK_ROLE_MENU_ITEM);
+    g_assert(atk_object_get_role(item2) == ATK_ROLE_MENU_ITEM);
+
+    /* Check the implementation of the AtkSelection interface. */
+    g_assert(ATK_IS_SELECTION(comboBox));
+    AtkSelection* atkSelection = ATK_SELECTION(comboBox);
+    g_assert_cmpint(atk_selection_get_selection_count(atkSelection), ==, 1);
+    g_assert(atk_selection_is_child_selected(atkSelection, 0));
+    g_assert(!atk_selection_is_child_selected(atkSelection, 1));
+    AtkObject* selectedItem = atk_selection_ref_selection(atkSelection, 0);
+    g_assert(selectedItem == item1);
+    g_object_unref(selectedItem);
+
+    /* Check the implementations of the AtkAction interface. */
+    g_assert(ATK_IS_ACTION(comboBox));
+    AtkAction* atkAction = ATK_ACTION(comboBox);
+    g_assert_cmpint(atk_action_get_n_actions(atkAction), ==, 1);
+    g_assert(atk_action_do_action(atkAction, 0));
+
+    g_assert(ATK_IS_ACTION(menuPopup));
+    atkAction = ATK_ACTION(menuPopup);
+    g_assert_cmpint(atk_action_get_n_actions(atkAction), ==, 1);
+    g_assert(atk_action_do_action(atkAction, 0));
+
+    g_assert(ATK_IS_ACTION(item1));
+    atkAction = ATK_ACTION(item1);
+    g_assert_cmpint(atk_action_get_n_actions(atkAction), ==, 1);
+    g_assert(atk_action_do_action(atkAction, 0));
+
+    g_assert(ATK_IS_ACTION(item2));
+    atkAction = ATK_ACTION(item2);
+    g_assert_cmpint(atk_action_get_n_actions(atkAction), ==, 1);
+    g_assert(atk_action_do_action(atkAction, 0));
+
+    /* After selecting the second item, selection should have changed. */
+    g_assert_cmpint(atk_selection_get_selection_count(atkSelection), ==, 1);
+    g_assert(!atk_selection_is_child_selected(atkSelection, 0));
+    g_assert(atk_selection_is_child_selected(atkSelection, 1));
+    selectedItem = atk_selection_ref_selection(atkSelection, 0);
+    g_assert(selectedItem == item2);
+    g_object_unref(selectedItem);
+
+    /* Check the implementation of the AtkText interface. */
+    g_assert(ATK_IS_TEXT(item1));
+    AtkText* atkText = ATK_TEXT(item1);
+    char *text = atk_text_get_text(atkText, 0, -1);
+    g_assert_cmpstr(text, ==, "foo");
+    g_free(text);
+    text = atk_text_get_text(atkText, 0, 2);
+    g_assert_cmpstr(text, ==, "fo");
+    g_free(text);
+
+    g_assert(ATK_IS_TEXT(item2));
+    atkText = ATK_TEXT(item2);
+    text = atk_text_get_text(atkText, 0, -1);
+    g_assert_cmpstr(text, ==, "bar");
+    g_free(text);
+    text = atk_text_get_text(atkText, 1, 3);
+    g_assert_cmpstr(text, ==, "ar");
+    g_free(text);
+
+    g_object_unref(formObject);
+    g_object_unref(comboBox);
+    g_object_unref(menuPopup);
+    g_object_unref(item1);
+    g_object_unref(item2);
+    g_object_unref(webView);
+}
+
 static void testWebkitAtkGetTextAtOffsetForms()
 {
     WebKitWebView* webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
@@ -1211,6 +1314,7 @@ int main(int argc, char** argv)
     gtk_test_init(&argc, &argv, 0);
 
     g_test_bug_base("https://bugs.webkit.org/");
+    g_test_add_func("/webkit/atk/comboBox", testWebkitAtkComboBox);
     g_test_add_func("/webkit/atk/getTextAtOffset", testWebkitAtkGetTextAtOffset);
     g_test_add_func("/webkit/atk/getTextAtOffsetForms", testWebkitAtkGetTextAtOffsetForms);
     g_test_add_func("/webkit/atk/getTextAtOffsetNewlines", testWebkitAtkGetTextAtOffsetNewlines);