3b58c0da0338b45665ea957b4c6bf9d8f0a24044
[WebKit-https.git] / Tools / WebKitTestRunner / InjectedBundle / atk / AccessibilityControllerAtk.cpp
1 /*
2  * Copyright (C) 2012 Igalia S.L.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include "config.h"
29 #include "AccessibilityController.h"
30
31 #include "InjectedBundle.h"
32 #include "InjectedBundlePage.h"
33
34 #include <WebKit2/WKBundlePagePrivate.h>
35 #include <atk/atk.h>
36 #include <cstdio>
37 #include <wtf/gobject/GOwnPtr.h>
38 #include <wtf/text/StringBuilder.h>
39
40 namespace WTR {
41
42 static void printAccessibilityEvent(AtkObject* accessible, const gchar* signalName, const gchar* signalValue)
43 {
44     // Do not handle state-change:defunct signals, as the AtkObject
45     // associated to them will not be valid at this point already.
46     if (!signalName || !g_strcmp0(signalName, "state-change:defunct"))
47         return;
48
49     if (!accessible || !ATK_IS_OBJECT(accessible))
50         return;
51
52     const gchar* objectName = atk_object_get_name(accessible);
53     AtkRole objectRole = atk_object_get_role(accessible);
54
55     // Try to always provide a name to be logged for the object.
56     if (!objectName || *objectName == '\0')
57         objectName = "(No name)";
58
59     GOwnPtr<gchar> signalNameAndValue(signalValue ? g_strdup_printf("%s = %s", signalName, signalValue) : g_strdup(signalName));
60     GOwnPtr<gchar> accessibilityEventString(g_strdup_printf("Accessibility object emitted \"%s\" / Name: \"%s\" / Role: %d\n", signalNameAndValue.get(), objectName, objectRole));
61     InjectedBundle::shared().outputText(String::fromUTF8(accessibilityEventString.get()));
62 }
63
64 static gboolean axObjectEventListener(GSignalInvocationHint *signalHint, guint numParamValues, const GValue *paramValues, gpointer data)
65 {
66     // At least we should receive the instance emitting the signal.
67     if (numParamValues < 1)
68         return TRUE;
69
70     AtkObject* accessible = ATK_OBJECT(g_value_get_object(&paramValues[0]));
71     if (!accessible || !ATK_IS_OBJECT(accessible))
72         return TRUE;
73
74     GSignalQuery signalQuery;
75     GOwnPtr<gchar> signalName;
76     GOwnPtr<gchar> signalValue;
77
78     g_signal_query(signalHint->signal_id, &signalQuery);
79
80     if (!g_strcmp0(signalQuery.signal_name, "state-change")) {
81         signalName.set(g_strdup_printf("state-change:%s", g_value_get_string(&paramValues[1])));
82         signalValue.set(g_strdup_printf("%d", g_value_get_boolean(&paramValues[2])));
83     } else if (!g_strcmp0(signalQuery.signal_name, "focus-event")) {
84         signalName.set(g_strdup("focus-event"));
85         signalValue.set(g_strdup_printf("%d", g_value_get_boolean(&paramValues[1])));
86     } else if (!g_strcmp0(signalQuery.signal_name, "children-changed")) {
87         signalName.set(g_strdup("children-changed"));
88         signalValue.set(g_strdup_printf("%d", g_value_get_uint(&paramValues[1])));
89     } else if (!g_strcmp0(signalQuery.signal_name, "property-change"))
90         signalName.set(g_strdup_printf("property-change:%s", g_quark_to_string(signalHint->detail)));
91     else
92         signalName.set(g_strdup(signalQuery.signal_name));
93
94     printAccessibilityEvent(accessible, signalName.get(), signalValue.get());
95
96     return TRUE;
97 }
98
99 void AccessibilityController::logAccessibilityEvents()
100 {
101     // Ensure no callbacks are connected before.
102     resetToConsistentState();
103
104     // Ensure that accessibility is initialized for the WebView by querying for
105     // the root accessible object, which will create the full hierarchy.
106     rootElement();
107
108     // Add global listeners for AtkObject's signals.
109     m_stateChangeListenerId = atk_add_global_event_listener(axObjectEventListener, "ATK:AtkObject:state-change");
110     m_focusEventListenerId = atk_add_global_event_listener(axObjectEventListener, "ATK:AtkObject:focus-event");
111     m_activeDescendantChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "ATK:AtkObject:active-descendant-changed");
112     m_childrenChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "ATK:AtkObject:children-changed");
113     m_propertyChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "ATK:AtkObject:property-change");
114     m_visibleDataChangedListenerId = atk_add_global_event_listener(axObjectEventListener, "ATK:AtkObject:visible-data-changed");
115
116     // Ensure the Atk interface types are registered, otherwise
117     // the AtkDocument signal handlers below won't get registered.
118     GObject* dummyAxObject = G_OBJECT(g_object_new(ATK_TYPE_OBJECT, 0));
119     AtkObject* dummyNoOpAxObject = atk_no_op_object_new(dummyAxObject);
120     g_object_unref(G_OBJECT(dummyNoOpAxObject));
121     g_object_unref(dummyAxObject);
122 }
123
124 void AccessibilityController::resetToConsistentState()
125 {
126     // AtkObject signals.
127     if (m_stateChangeListenerId) {
128         atk_remove_global_event_listener(m_stateChangeListenerId);
129         m_stateChangeListenerId = 0;
130     }
131     if (m_focusEventListenerId) {
132         atk_remove_global_event_listener(m_focusEventListenerId);
133         m_focusEventListenerId = 0;
134     }
135     if (m_activeDescendantChangedListenerId) {
136         atk_remove_global_event_listener(m_activeDescendantChangedListenerId);
137         m_activeDescendantChangedListenerId = 0;
138     }
139     if (m_childrenChangedListenerId) {
140         atk_remove_global_event_listener(m_childrenChangedListenerId);
141         m_childrenChangedListenerId = 0;
142     }
143     if (m_propertyChangedListenerId) {
144         atk_remove_global_event_listener(m_propertyChangedListenerId);
145         m_propertyChangedListenerId = 0;
146     }
147     if (m_visibleDataChangedListenerId) {
148         atk_remove_global_event_listener(m_visibleDataChangedListenerId);
149         m_visibleDataChangedListenerId = 0;
150     }
151 }
152
153 static AtkObject* childElementById(AtkObject* parent, const char* id)
154 {
155     if (!ATK_IS_OBJECT(parent))
156         return 0;
157
158     bool parentFound = false;
159     AtkAttributeSet* attributeSet = atk_object_get_attributes(parent);
160     for (AtkAttributeSet* attributes = attributeSet; attributes; attributes = attributes->next) {
161         AtkAttribute* attribute = static_cast<AtkAttribute*>(attributes->data);
162         if (!strcmp(attribute->name, "html-id")) {
163             if (!strcmp(attribute->value, id))
164                 parentFound = true;
165             break;
166         }
167     }
168     atk_attribute_set_free(attributeSet);
169
170     if (parentFound)
171         return parent;
172
173     int childCount = atk_object_get_n_accessible_children(parent);
174     for (int i = 0; i < childCount; i++) {
175         AtkObject* result = childElementById(atk_object_ref_accessible_child(parent, i), id);
176         if (ATK_IS_OBJECT(result))
177             return result;
178     }
179
180     return 0;
181 }
182
183 PassRefPtr<AccessibilityUIElement> AccessibilityController::accessibleElementById(JSStringRef id)
184 {
185     AtkObject* root = ATK_OBJECT(WKAccessibilityRootObject(InjectedBundle::shared().page()->page()));
186     if (!root)
187         return 0;
188
189     size_t bufferSize = JSStringGetMaximumUTF8CStringSize(id);
190     GOwnPtr<gchar> idBuffer(static_cast<gchar*>(g_malloc(bufferSize)));
191     JSStringGetUTF8CString(id, idBuffer.get(), bufferSize);
192
193     AtkObject* result = childElementById(root, idBuffer.get());
194     if (ATK_IS_OBJECT(result))
195         return AccessibilityUIElement::create(result);
196
197     return 0;
198 }
199
200 PassRefPtr<AccessibilityUIElement> AccessibilityController::rootElement()
201 {
202     WKBundlePageRef page = InjectedBundle::shared().page()->page();
203     void* root = WKAccessibilityRootObject(page);
204
205     return AccessibilityUIElement::create(static_cast<AtkObject*>(root));
206 }
207
208 PassRefPtr<AccessibilityUIElement> AccessibilityController::focusedElement()
209 {
210     WKBundlePageRef page = InjectedBundle::shared().page()->page();
211     void* root = WKAccessibilityFocusedObject(page);
212
213     return AccessibilityUIElement::create(static_cast<AtkObject*>(root));
214 }
215
216 } // namespace WTR