| /* |
| * Copyright (C) 2021 Igalia S.L. |
| * |
| * 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 "AccessibilityRootAtspi.h" |
| |
| #if USE(ATSPI) |
| #include "AXObjectCache.h" |
| #include "AccessibilityAtspiEnums.h" |
| #include "AccessibilityAtspiInterfaces.h" |
| #include "Document.h" |
| #include "FocusController.h" |
| #include "Frame.h" |
| #include "FrameView.h" |
| #include "Page.h" |
| #include <glib/gi18n-lib.h> |
| #include <locale.h> |
| |
| namespace WebCore { |
| |
| Ref<AccessibilityRootAtspi> AccessibilityRootAtspi::create(Page& page) |
| { |
| return adoptRef(*new AccessibilityRootAtspi(page)); |
| } |
| |
| AccessibilityRootAtspi::AccessibilityRootAtspi(Page& page) |
| : m_page(page) |
| { |
| } |
| |
| GDBusInterfaceVTable AccessibilityRootAtspi::s_accessibleFunctions = { |
| // method_call |
| [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) { |
| auto& rootObject = *static_cast<AccessibilityRootAtspi*>(userData); |
| if (!g_strcmp0(methodName, "GetRole")) |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", Atspi::Role::Filler)); |
| else if (!g_strcmp0(methodName, "GetRoleName")) |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", "filler")); |
| else if (!g_strcmp0(methodName, "GetLocalizedRoleName")) |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", _("filler"))); |
| else if (!g_strcmp0(methodName, "GetState")) { |
| GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("(au)")); |
| |
| g_variant_builder_open(&builder, G_VARIANT_TYPE("au")); |
| g_variant_builder_add(&builder, "u", 0); |
| g_variant_builder_add(&builder, "u", 0); |
| g_variant_builder_close(&builder); |
| |
| g_dbus_method_invocation_return_value(invocation, g_variant_builder_end(&builder)); |
| } else if (!g_strcmp0(methodName, "GetAttributes")) { |
| GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("(a{ss})")); |
| |
| g_variant_builder_open(&builder, G_VARIANT_TYPE("a{ss}")); |
| #if PLATFORM(GTK) |
| g_variant_builder_add(&builder, "{ss}", "toolkit", "WebKitGTK"); |
| #elif PLATFORM(WPE) |
| g_variant_builder_add(&builder, "{ss}", "toolkit", "WPEWebKit"); |
| #endif |
| g_variant_builder_close(&builder); |
| |
| g_dbus_method_invocation_return_value(invocation, g_variant_builder_end(&builder)); |
| } else if (!g_strcmp0(methodName, "GetApplication")) |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("(@(so))", rootObject.applicationReference())); |
| else if (!g_strcmp0(methodName, "GetChildAtIndex")) { |
| int index; |
| g_variant_get(parameters, "(i)", &index); |
| if (!index) { |
| if (auto* child = rootObject.child()) { |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("(@(so))", child->reference())); |
| return; |
| } |
| } |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("(@(so))", AccessibilityAtspi::singleton().nullReference())); |
| } else if (!g_strcmp0(methodName, "GetChildren")) { |
| GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("a(so)")); |
| if (auto* child = rootObject.child()) |
| g_variant_builder_add(&builder, "@(so)", child->reference()); |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(so))", &builder)); |
| } else if (!g_strcmp0(methodName, "GetIndexInParent")) { |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", 0)); |
| } else if (!g_strcmp0(methodName, "GetRelationSet")) { |
| GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("a(ua(so))")); |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(ua(so)))", &builder)); |
| } else if (!g_strcmp0(methodName, "GetInterfaces")) { |
| GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("as")); |
| |
| g_variant_builder_add(&builder, "s", webkit_accessible_interface.name); |
| g_variant_builder_add(&builder, "s", webkit_component_interface.name); |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("(as)", &builder)); |
| } |
| }, |
| // get_property |
| [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* propertyName, GError** error, gpointer userData) -> GVariant* { |
| auto& rootObject = *static_cast<AccessibilityRootAtspi*>(userData); |
| if (!g_strcmp0(propertyName, "Name")) |
| return g_variant_new_string(""); |
| if (!g_strcmp0(propertyName, "Description")) |
| return g_variant_new_string(""); |
| if (!g_strcmp0(propertyName, "Locale")) |
| return g_variant_new_string(setlocale(LC_MESSAGES, nullptr)); |
| if (!g_strcmp0(propertyName, "AccessibleId")) |
| return g_variant_new_string(""); |
| if (!g_strcmp0(propertyName, "Parent")) |
| return rootObject.parentReference(); |
| if (!g_strcmp0(propertyName, "ChildCount")) |
| return g_variant_new_int32(rootObject.child() ? 1 : 0); |
| |
| g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unknown property '%s'", propertyName); |
| return nullptr; |
| }, |
| // set_property, |
| nullptr, |
| // padding |
| { nullptr } |
| }; |
| |
| GDBusInterfaceVTable AccessibilityRootAtspi::s_socketFunctions = { |
| // method_call |
| [](GDBusConnection*, const gchar* sender, const gchar*, const gchar*, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) { |
| auto& rootObject = *static_cast<AccessibilityRootAtspi*>(userData); |
| if (!g_strcmp0(methodName, "Embedded")) { |
| const char* path; |
| g_variant_get(parameters, "(&s)", &path); |
| rootObject.embedded(sender, path); |
| g_dbus_method_invocation_return_value(invocation, nullptr); |
| } |
| }, |
| // get_property |
| nullptr, |
| // set_property, |
| nullptr, |
| // padding |
| { nullptr } |
| }; |
| |
| void AccessibilityRootAtspi::registerObject(CompletionHandler<void(const String&)>&& completionHandler) |
| { |
| if (m_page) |
| m_page->setAccessibilityRootObject(this); |
| |
| Vector<std::pair<GDBusInterfaceInfo*, GDBusInterfaceVTable*>> interfaces; |
| interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_accessible_interface), &s_accessibleFunctions }); |
| interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_socket_interface), &s_socketFunctions }); |
| interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_component_interface), &s_componentFunctions }); |
| AccessibilityAtspi::singleton().registerRoot(*this, WTFMove(interfaces), WTFMove(completionHandler)); |
| } |
| |
| void AccessibilityRootAtspi::unregisterObject() |
| { |
| AccessibilityAtspi::singleton().unregisterRoot(*this); |
| |
| if (m_page) |
| m_page->setAccessibilityRootObject(nullptr); |
| } |
| |
| void AccessibilityRootAtspi::setPath(String&& path) |
| { |
| m_path = WTFMove(path); |
| } |
| |
| void AccessibilityRootAtspi::embedded(const char* parentUniqueName, const char* parentPath) |
| { |
| m_parentUniqueName = String::fromUTF8(parentUniqueName); |
| m_parentPath = String::fromUTF8(parentPath); |
| AccessibilityAtspi::singleton().parentChanged(*this); |
| } |
| |
| GVariant* AccessibilityRootAtspi::applicationReference() const |
| { |
| if (m_parentUniqueName.isNull()) |
| return AccessibilityAtspi::singleton().nullReference(); |
| return g_variant_new("(so)", m_parentUniqueName.utf8().data(), "/org/a11y/atspi/accessible/root"); |
| } |
| |
| GVariant* AccessibilityRootAtspi::reference() const |
| { |
| return g_variant_new("(so)", AccessibilityAtspi::singleton().uniqueName(), m_path.utf8().data()); |
| } |
| |
| GVariant* AccessibilityRootAtspi::parentReference() const |
| { |
| if (m_parentUniqueName.isNull()) |
| return AccessibilityAtspi::singleton().nullReference(); |
| return g_variant_new("(so)", m_parentUniqueName.utf8().data(), m_parentPath.utf8().data()); |
| } |
| |
| AccessibilityObjectAtspi* AccessibilityRootAtspi::child() const |
| { |
| if (!m_page) |
| return nullptr; |
| |
| Frame& frame = m_page->mainFrame(); |
| if (!frame.document()) |
| return nullptr; |
| |
| AXObjectCache::enableAccessibility(); |
| AXObjectCache* cache = frame.document()->axObjectCache(); |
| if (!cache) |
| return nullptr; |
| |
| AXCoreObject* rootObject = cache->rootObject(); |
| return rootObject ? rootObject->wrapper() : nullptr; |
| } |
| |
| void AccessibilityRootAtspi::childAdded(AccessibilityObjectAtspi& child) |
| { |
| AccessibilityAtspi::singleton().childrenChanged(*this, child, AccessibilityAtspi::ChildrenChanged::Added); |
| } |
| |
| void AccessibilityRootAtspi::childRemoved(AccessibilityObjectAtspi& child) |
| { |
| AccessibilityAtspi::singleton().childrenChanged(*this, child, AccessibilityAtspi::ChildrenChanged::Removed); |
| } |
| |
| void AccessibilityRootAtspi::serialize(GVariantBuilder* builder) const |
| { |
| g_variant_builder_add(builder, "(so)", AccessibilityAtspi::singleton().uniqueName(), m_path.utf8().data()); |
| g_variant_builder_add(builder, "@(so)", applicationReference()); |
| g_variant_builder_add(builder, "@(so)", parentReference()); |
| |
| g_variant_builder_add(builder, "i", 0); |
| // Do not set the children count in cache, because child is handled by children-changed signals. |
| g_variant_builder_add(builder, "i", -1); |
| |
| GVariantBuilder interfaces = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("as")); |
| g_variant_builder_add(&interfaces, "s", webkit_accessible_interface.name); |
| g_variant_builder_add(&interfaces, "s", webkit_component_interface.name); |
| g_variant_builder_add(builder, "@as", g_variant_new("as", &interfaces)); |
| |
| g_variant_builder_add(builder, "s", ""); |
| |
| g_variant_builder_add(builder, "u", Atspi::Role::Filler); |
| |
| g_variant_builder_add(builder, "s", ""); |
| |
| GVariantBuilder states = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("au")); |
| g_variant_builder_add(&states, "u", 0); |
| g_variant_builder_add(&states, "u", 0); |
| g_variant_builder_add(builder, "@au", g_variant_builder_end(&states)); |
| } |
| |
| GDBusInterfaceVTable AccessibilityRootAtspi::s_componentFunctions = { |
| // method_call |
| [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) { |
| auto& rootObject = *static_cast<AccessibilityRootAtspi*>(userData); |
| if (!g_strcmp0(methodName, "Contains")) |
| g_dbus_method_invocation_return_error_literal(invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); |
| else if (!g_strcmp0(methodName, "GetAccessibleAtPoint")) |
| g_dbus_method_invocation_return_error_literal(invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); |
| else if (!g_strcmp0(methodName, "GetExtents")) { |
| uint32_t coordinateType; |
| g_variant_get(parameters, "(u)", &coordinateType); |
| auto rect = rootObject.frameRect(coordinateType); |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("((iiii))", rect.x(), rect.y(), rect.width(), rect.height())); |
| } else if (!g_strcmp0(methodName, "GetPosition")) { |
| uint32_t coordinateType; |
| g_variant_get(parameters, "(u)", &coordinateType); |
| auto rect = rootObject.frameRect(coordinateType); |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("((ii))", rect.x(), rect.y())); |
| } else if (!g_strcmp0(methodName, "GetSize")) { |
| auto rect = rootObject.frameRect(Atspi::CoordinateType::ParentCoordinates); |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("((ii))", rect.width(), rect.height())); |
| } else if (!g_strcmp0(methodName, "GetLayer")) |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", Atspi::ComponentLayer::WidgetLayer)); |
| else if (!g_strcmp0(methodName, "GetMDIZOrder")) |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("(n)", 0)); |
| else if (!g_strcmp0(methodName, "GrabFocus")) |
| g_dbus_method_invocation_return_error_literal(invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); |
| else if (!g_strcmp0(methodName, "GetAlpha")) |
| g_dbus_method_invocation_return_value(invocation, g_variant_new("(d)", 1.0)); |
| else if ((!g_strcmp0(methodName, "SetExtents")) || !g_strcmp0(methodName, "SetPosition") || !g_strcmp0(methodName, "SetSize") || !g_strcmp0(methodName, "ScrollTo") || !g_strcmp0(methodName, "ScrollToPoint")) |
| g_dbus_method_invocation_return_error_literal(invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, ""); |
| }, |
| // get_property |
| nullptr, |
| // set_property, |
| nullptr, |
| // padding |
| { nullptr } |
| }; |
| |
| IntRect AccessibilityRootAtspi::frameRect(uint32_t coordinateType) const |
| { |
| if (!m_page) |
| return { }; |
| |
| auto* frameView = m_page->mainFrame().view(); |
| if (!frameView) |
| return { }; |
| |
| auto frameRect = frameView->frameRect(); |
| switch (coordinateType) { |
| case Atspi::CoordinateType::ScreenCoordinates: |
| return frameView->contentsToScreen(frameRect); |
| case Atspi::CoordinateType::WindowCoordinates: |
| return frameView->contentsToWindow(frameRect); |
| case Atspi::CoordinateType::ParentCoordinates: |
| return frameRect; |
| } |
| |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| |
| } // namespace WebCore |
| |
| #endif // USE(ATSPI) |