diff options
Diffstat (limited to 'Tools/WebKitTestRunner/InjectedBundle/atk/AccessibilityNotificationHandlerAtk.cpp')
-rw-r--r-- | Tools/WebKitTestRunner/InjectedBundle/atk/AccessibilityNotificationHandlerAtk.cpp | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/Tools/WebKitTestRunner/InjectedBundle/atk/AccessibilityNotificationHandlerAtk.cpp b/Tools/WebKitTestRunner/InjectedBundle/atk/AccessibilityNotificationHandlerAtk.cpp new file mode 100644 index 000000000..95cf8ae4b --- /dev/null +++ b/Tools/WebKitTestRunner/InjectedBundle/atk/AccessibilityNotificationHandlerAtk.cpp @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2013 Samsung Electronics Inc. All rights reserved. + * + * 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; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "config.h" +#include "AccessibilityNotificationHandlerAtk.h" + +#if HAVE(ACCESSIBILITY) + +#include "InjectedBundle.h" +#include "InjectedBundlePage.h" +#include "JSWrapper.h" +#include <WebKit/WKBundleFrame.h> +#include <WebKit/WKBundlePage.h> +#include <WebKit/WKBundlePagePrivate.h> +#include <wtf/HashMap.h> +#include <wtf/Vector.h> +#include <wtf/glib/GUniquePtr.h> +#include <wtf/text/CString.h> +#include <wtf/text/WTFString.h> + +namespace WTR { + +namespace { + +typedef HashMap<AtkObject*, AccessibilityNotificationHandler*> NotificationHandlersMap; + +WTF::Vector<unsigned> listenerIds; +NotificationHandlersMap notificationHandlers; +AccessibilityNotificationHandler* globalNotificationHandler = nullptr; + +gboolean axObjectEventListener(GSignalInvocationHint* signalHint, unsigned numParamValues, const GValue* paramValues, gpointer data) +{ + // At least we should receive the instance emitting the signal. + if (!numParamValues) + return true; + + AtkObject* accessible = ATK_OBJECT(g_value_get_object(¶mValues[0])); + if (!accessible || !ATK_IS_OBJECT(accessible)) + return true; + +#if PLATFORM(GTK) || PLATFORM(EFL) + WKBundlePageRef page = InjectedBundle::singleton().page()->page(); + WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(page); + JSContextRef jsContext = WKBundleFrameGetJavaScriptContext(mainFrame); +#else + JSContextRef jsContext = nullptr; +#endif + + GSignalQuery signalQuery; + const char* notificationName = nullptr; + Vector<JSValueRef> extraArgs; + + g_signal_query(signalHint->signal_id, &signalQuery); + + if (!g_strcmp0(signalQuery.signal_name, "state-change")) { + if (!g_strcmp0(g_value_get_string(¶mValues[1]), "checked")) + notificationName = "CheckedStateChanged"; + else if (!g_strcmp0(g_value_get_string(¶mValues[1]), "invalid-entry")) + notificationName = "AXInvalidStatusChanged"; + } else if (!g_strcmp0(signalQuery.signal_name, "focus-event")) { + if (g_value_get_boolean(¶mValues[1])) + notificationName = "AXFocusedUIElementChanged"; + } else if (!g_strcmp0(signalQuery.signal_name, "selection-changed")) { + notificationName = "AXSelectedChildrenChanged"; + } else if (!g_strcmp0(signalQuery.signal_name, "children-changed")) { + const gchar* childrenChangedDetail = g_quark_to_string(signalHint->detail); + notificationName = !g_strcmp0(childrenChangedDetail, "add") ? "AXChildrenAdded" : "AXChildrenRemoved"; + } else if (!g_strcmp0(signalQuery.signal_name, "property-change")) { + if (!g_strcmp0(g_quark_to_string(signalHint->detail), "accessible-value")) + notificationName = "AXValueChanged"; + } else if (!g_strcmp0(signalQuery.signal_name, "load-complete")) + notificationName = "AXLoadComplete"; + else if (!g_strcmp0(signalQuery.signal_name, "text-caret-moved")) { + notificationName = "AXTextCaretMoved"; + GUniquePtr<char> signalValue(g_strdup_printf("%d", g_value_get_int(¶mValues[1]))); + JSRetainPtr<JSStringRef> jsSignalValue(Adopt, JSStringCreateWithUTF8CString(signalValue.get())); + extraArgs.append(JSValueMakeString(jsContext, jsSignalValue.get())); + } + + if (!jsContext) + return true; + + if (notificationName) { + JSRetainPtr<JSStringRef> jsNotificationEventName(Adopt, JSStringCreateWithUTF8CString(notificationName)); + JSValueRef notificationNameArgument = JSValueMakeString(jsContext, jsNotificationEventName.get()); + NotificationHandlersMap::iterator elementNotificationHandler = notificationHandlers.find(accessible); + JSValueRef arguments[5]; // this dimension must be >= 2 + max(extraArgs.size()) + arguments[0] = toJS(jsContext, WTF::getPtr(WTR::AccessibilityUIElement::create(accessible))); + arguments[1] = notificationNameArgument; + size_t numOfExtraArgs = extraArgs.size(); + for (int i = 0; i < numOfExtraArgs; i++) + arguments[i + 2] = extraArgs[i]; + if (elementNotificationHandler != notificationHandlers.end()) { + // Listener for one element. As arguments, it gets the notification name + // and sometimes extra arguments. + JSObjectCallAsFunction(jsContext, + const_cast<JSObjectRef>(elementNotificationHandler->value->notificationFunctionCallback()), + 0, numOfExtraArgs + 1, arguments + 1, 0); + } + + if (globalNotificationHandler) { + // A global listener gets additionally the element as the first argument. + JSObjectCallAsFunction(jsContext, + const_cast<JSObjectRef>(globalNotificationHandler->notificationFunctionCallback()), + 0, numOfExtraArgs + 2, arguments, 0); + } + } + + return true; +} + +} // namespace + +AccessibilityNotificationHandler::AccessibilityNotificationHandler() + : m_platformElement(0) + , m_notificationFunctionCallback(0) +{ +} + +AccessibilityNotificationHandler::~AccessibilityNotificationHandler() +{ + removeAccessibilityNotificationHandler(); + disconnectAccessibilityCallbacks(); +} + +void AccessibilityNotificationHandler::setNotificationFunctionCallback(JSValueRef notificationFunctionCallback) +{ + if (!notificationFunctionCallback) { + removeAccessibilityNotificationHandler(); + disconnectAccessibilityCallbacks(); + return; + } + + m_notificationFunctionCallback = notificationFunctionCallback; + +#if PLATFORM(GTK) || PLATFORM(EFL) + WKBundlePageRef page = InjectedBundle::singleton().page()->page(); + WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(page); + JSContextRef jsContext = WKBundleFrameGetJavaScriptContext(mainFrame); +#else + JSContextRef jsContext = nullptr; +#endif + if (!jsContext) + return; + + connectAccessibilityCallbacks(); + + JSValueProtect(jsContext, m_notificationFunctionCallback); + // Check if this notification handler is related to a specific element. + if (m_platformElement) { + NotificationHandlersMap::iterator currentNotificationHandler = notificationHandlers.find(m_platformElement.get()); + if (currentNotificationHandler != notificationHandlers.end()) { + ASSERT(currentNotificationHandler->value->platformElement()); + JSValueUnprotect(jsContext, currentNotificationHandler->value->notificationFunctionCallback()); + notificationHandlers.remove(currentNotificationHandler->value->platformElement().get()); + } + notificationHandlers.add(m_platformElement.get(), this); + } else { + if (globalNotificationHandler) + JSValueUnprotect(jsContext, globalNotificationHandler->notificationFunctionCallback()); + globalNotificationHandler = this; + } +} + +void AccessibilityNotificationHandler::removeAccessibilityNotificationHandler() +{ +#if PLATFORM(GTK) || PLATFORM(EFL) + WKBundlePageRef page = InjectedBundle::singleton().page()->page(); + WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(page); + JSContextRef jsContext = WKBundleFrameGetJavaScriptContext(mainFrame); +#else + JSContextRef jsContext = nullptr; +#endif + if (!jsContext) + return; + + if (globalNotificationHandler == this) { + JSValueUnprotect(jsContext, globalNotificationHandler->notificationFunctionCallback()); + globalNotificationHandler = nullptr; + } else if (m_platformElement.get()) { + NotificationHandlersMap::iterator removeNotificationHandler = notificationHandlers.find(m_platformElement.get()); + if (removeNotificationHandler != notificationHandlers.end()) { + JSValueUnprotect(jsContext, removeNotificationHandler->value->notificationFunctionCallback()); + notificationHandlers.remove(removeNotificationHandler); + } + } +} + +void AccessibilityNotificationHandler::connectAccessibilityCallbacks() +{ + // Ensure no callbacks are connected before. + if (!disconnectAccessibilityCallbacks()) + return; + + const char* signalNames[] = { + "ATK:AtkObject:state-change", + "ATK:AtkObject:focus-event", + "ATK:AtkObject:active-descendant-changed", + "ATK:AtkObject:children-changed", + "ATK:AtkObject:property-change", + "ATK:AtkObject:visible-data-changed", + "ATK:AtkDocument:load-complete", + "ATK:AtkSelection:selection-changed", + "ATK:AtkText:text-caret-moved", + 0 + }; + + // Register atk interfaces, otherwise add_global may fail. + GObject* dummyObject = (GObject*)g_object_new(G_TYPE_OBJECT, NULL, NULL); + g_object_unref(atk_no_op_object_new(dummyObject)); + g_object_unref(dummyObject); + + // Add global listeners for AtkObject's signals. + for (const char** signalName = signalNames; *signalName; signalName++) { + unsigned id = atk_add_global_event_listener(axObjectEventListener, *signalName); + if (!id) { + String message = String::format("atk_add_global_event_listener failed for signal %s\n", *signalName); + InjectedBundle::singleton().outputText(message); + continue; + } + + listenerIds.append(id); + } +} + +bool AccessibilityNotificationHandler::disconnectAccessibilityCallbacks() +{ + // Only disconnect if there is no notification handler. + if (!notificationHandlers.isEmpty() || globalNotificationHandler) + return false; + + // AtkObject signals. + for (int i = 0; i < listenerIds.size(); i++) { + ASSERT(listenerIds[i]); + atk_remove_global_event_listener(listenerIds[i]); + } + listenerIds.clear(); + return true; +} + +} // namespace WTR + +#endif // HAVE(ACCESSIBILITY) |