Added: trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectHyperlinkAtspi.cpp (0 => 286050)
--- trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectHyperlinkAtspi.cpp (rev 0)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectHyperlinkAtspi.cpp 2021-11-19 08:51:53 UTC (rev 286050)
@@ -0,0 +1,115 @@
+/*
+ * 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 "AccessibilityObjectAtspi.h"
+
+#if ENABLE(ACCESSIBILITY) && USE(ATSPI)
+
+#include "AccessibilityRootAtspi.h"
+#include <gio/gio.h>
+#include <wtf/URL.h>
+#include <wtf/unicode/CharacterNames.h>
+
+namespace WebCore {
+
+GDBusInterfaceVTable AccessibilityObjectAtspi::s_hyperlinkFunctions = {
+ // method_call
+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) {
+ RELEASE_ASSERT(!isMainThread());
+ auto atspiObject = Ref { *static_cast<AccessibilityObjectAtspi*>(userData) };
+ atspiObject->updateBackingStore();
+
+ if (!g_strcmp0(methodName, "GetObject")) {
+ int index;
+ g_variant_get(parameters, "(i)", &index);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(@(so))", !index ? atspiObject->reference() : atspiObject->root()->atspi().nullReference()));
+ } else if (!g_strcmp0(methodName, "GetURI")) {
+ int index;
+ g_variant_get(parameters, "(i)", &index);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", !index ? atspiObject->url().string().utf8().data() : ""));
+ } else if (!g_strcmp0(methodName, "IsValid"))
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", atspiObject->m_axObject ? TRUE : FALSE));
+ },
+ // get_property
+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* propertyName, GError** error, gpointer userData) -> GVariant* {
+ RELEASE_ASSERT(!isMainThread());
+ auto atspiObject = Ref { *static_cast<AccessibilityObjectAtspi*>(userData) };
+ atspiObject->updateBackingStore();
+
+ if (!g_strcmp0(propertyName, "NAnchors"))
+ return g_variant_new_int32(1);
+ if (!g_strcmp0(propertyName, "StartIndex"))
+ return g_variant_new_int32(atspiObject->offsetInParent());
+ if (!g_strcmp0(propertyName, "EndIndex"))
+ return g_variant_new_int32(atspiObject->offsetInParent() + 1);
+
+ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unknown property '%s'", propertyName);
+ return nullptr;
+ },
+ // set_property,
+ nullptr,
+ // padding
+ nullptr
+};
+
+URL AccessibilityObjectAtspi::url() const
+{
+ AXCoreObject* axObject = isMainThread() ? m_coreObject : m_axObject;
+ return axObject ? axObject->url() : URL();
+}
+
+unsigned AccessibilityObjectAtspi::offsetInParent() const
+{
+ return Accessibility::retrieveValueFromMainThread<unsigned>([this]() -> unsigned {
+ if (m_coreObject)
+ m_coreObject->updateBackingStore();
+
+ if (!m_coreObject)
+ return 0;
+
+ auto* parent = m_coreObject->parentObjectUnignored();
+ if (!parent || !parent->wrapper())
+ return 0;
+
+ int index = -1;
+ const auto& children = parent->children();
+ for (const auto& child : children) {
+ if (child->accessibilityIsIgnored())
+ continue;
+
+ auto* wrapper = child->wrapper();
+ if (!wrapper || !wrapper->interfaces().contains(Interface::Hyperlink))
+ continue;
+
+ index++;
+ if (wrapper == this)
+ break;
+ }
+
+ if (index == -1)
+ return 0;
+
+ return parent->wrapper()->characterOffset(objectReplacementCharacter, index).value_or(0);
+ });
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(ACCESSIBILITY) && USE(ATSPI)
Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp (286049 => 286050)
--- trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp 2021-11-19 07:30:40 UTC (rev 286049)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp 2021-11-19 08:51:53 UTC (rev 286050)
@@ -1533,6 +1533,7 @@
"<html>"
" <body>"
" <p>This is <button>button1</button> and <button>button2</button> in paragraph</p>"
+ " <p>This is <a href=''>link1</a> and <a href=''>link2</a> in paragraph</p>"
" </body>"
"</html>",
nullptr);
@@ -1543,7 +1544,7 @@
auto documentWeb = test->findDocumentWeb(testApp.get());
g_assert_true(ATSPI_IS_ACCESSIBLE(documentWeb.get()));
- g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 1);
+ g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 2);
auto p = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
g_assert_true(ATSPI_IS_TEXT(p.get()));
@@ -1611,6 +1612,24 @@
g_assert_cmpuint(g_hash_table_size(attributes.get()), ==, 0);
g_assert_cmpint(startOffset, ==, 15);
g_assert_cmpint(endOffset, ==, 28);
+
+ // Links are also replaced elements.
+ p = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 1, nullptr));
+ g_assert_true(ATSPI_IS_TEXT(p.get()));
+ g_assert_cmpint(atspi_text_get_character_count(ATSPI_TEXT(p.get()), nullptr), ==, 28);
+ text.reset(atspi_text_get_text(ATSPI_TEXT(p.get()), 0, -1, nullptr));
+ g_assert_cmpstr(text.get(), ==, "This is \357\277\274 and \357\277\274 in paragraph");
+ g_assert_cmpint(atspi_accessible_get_child_count(p.get(), nullptr), ==, 2);
+
+ auto link1 = adoptGRef(atspi_accessible_get_child_at_index(p.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_TEXT(link1.get()));
+ text.reset(atspi_text_get_text(ATSPI_TEXT(link1.get()), 0, -1, nullptr));
+ g_assert_cmpstr(text.get(), ==, "link1");
+
+ auto link2 = adoptGRef(atspi_accessible_get_child_at_index(p.get(), 1, nullptr));
+ g_assert_true(ATSPI_IS_TEXT(link2.get()));
+ text.reset(atspi_text_get_text(ATSPI_TEXT(link2.get()), 0, -1, nullptr));
+ g_assert_cmpstr(text.get(), ==, "link2");
#endif
}
@@ -1665,6 +1684,139 @@
g_assert_cmpfloat(atspi_value_get_current_value(ATSPI_VALUE(slider.get()), nullptr), ==, 0);
}
+static void testHyperlinkBasic(AccessibilityTest* test, gconstpointer)
+{
+ test->showInWindow(800, 600);
+ test->loadHtml(
+ "<html>"
+ " <body>"
+ " <a href=''>WebKitGTK</a>"
+ " <div role='link'>Link</div>"
+ " <p>This is <button>button1</button> and <button>button2</button> in a paragraph</p>"
+ " <p>This is <a href=''>link1</a> and <a href=''>link2</a> in paragraph</p>"
+ " </body>"
+ "</html>",
+ nullptr);
+ test->waitUntilLoadFinished();
+
+ auto testApp = test->findTestApplication();
+ g_assert_true(ATSPI_IS_ACCESSIBLE(testApp.get()));
+
+ auto documentWeb = test->findDocumentWeb(testApp.get());
+ g_assert_true(ATSPI_IS_ACCESSIBLE(documentWeb.get()));
+ g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 4);
+
+ auto section = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_ACCESSIBLE(section.get()));
+ g_assert_cmpint(atspi_accessible_get_role(section.get(), nullptr), ==, ATSPI_ROLE_SECTION);
+ g_assert_cmpint(atspi_accessible_get_child_count(section.get(), nullptr), ==, 1);
+
+ auto a = adoptGRef(atspi_accessible_get_child_at_index(section.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_ACCESSIBLE(a.get()));
+ g_assert_cmpint(atspi_accessible_get_role(a.get(), nullptr), ==, ATSPI_ROLE_LINK);
+ auto link = adoptGRef(atspi_accessible_get_hyperlink(a.get()));
+ g_assert_true(ATSPI_IS_HYPERLINK(link.get()));
+ g_assert_cmpint(atspi_hyperlink_get_n_anchors(ATSPI_HYPERLINK(link.get()), nullptr), ==, 1);
+ g_assert_true(atspi_hyperlink_is_valid(ATSPI_HYPERLINK(link.get()), nullptr));
+ g_assert_cmpint(atspi_hyperlink_get_start_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 0);
+#if USE(ATSPI)
+ g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 1);
+#else
+ g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 0);
+#endif
+ GUniquePtr<char> uri(atspi_hyperlink_get_uri(ATSPI_HYPERLINK(link.get()), 0, nullptr));
+ g_assert_cmpstr(uri.get(), ==, "https://www.webkitgtk.org/");
+ g_assert_true(atspi_hyperlink_get_object(ATSPI_HYPERLINK(link.get()), 0, nullptr) == a.get());
+
+ auto div = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 1, nullptr));
+ g_assert_true(ATSPI_IS_ACCESSIBLE(div.get()));
+ g_assert_cmpint(atspi_accessible_get_role(div.get(), nullptr), ==, ATSPI_ROLE_LINK);
+ link = adoptGRef(atspi_accessible_get_hyperlink(div.get()));
+ g_assert_true(ATSPI_IS_HYPERLINK(link.get()));
+ g_assert_cmpint(atspi_hyperlink_get_n_anchors(ATSPI_HYPERLINK(link.get()), nullptr), ==, 1);
+ g_assert_true(atspi_hyperlink_is_valid(ATSPI_HYPERLINK(link.get()), nullptr));
+#if USE(ATSPI)
+ g_assert_cmpint(atspi_hyperlink_get_start_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 0);
+ g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 1);
+#else
+ g_assert_cmpint(atspi_hyperlink_get_start_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 10);
+ g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 14);
+#endif
+ uri.reset(atspi_hyperlink_get_uri(ATSPI_HYPERLINK(link.get()), 0, nullptr));
+ g_assert_cmpstr(uri.get(), ==, "");
+ g_assert_true(atspi_hyperlink_get_object(ATSPI_HYPERLINK(link.get()), 0, nullptr) == div.get());
+
+ auto p = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 2, nullptr));
+ g_assert_true(ATSPI_IS_ACCESSIBLE(p.get()));
+ g_assert_cmpint(atspi_accessible_get_child_count(p.get(), nullptr), ==, 2);
+ auto button1 = adoptGRef(atspi_accessible_get_child_at_index(p.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_ACCESSIBLE(button1.get()));
+ link = adoptGRef(atspi_accessible_get_hyperlink(button1.get()));
+ g_assert_true(ATSPI_IS_HYPERLINK(link.get()));
+ g_assert_cmpint(atspi_hyperlink_get_n_anchors(ATSPI_HYPERLINK(link.get()), nullptr), ==, 1);
+ g_assert_true(atspi_hyperlink_is_valid(ATSPI_HYPERLINK(link.get()), nullptr));
+ g_assert_cmpint(atspi_hyperlink_get_start_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 8);
+#if USE(ATSPI)
+ g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 9);
+#else
+ g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 16);
+#endif
+ uri.reset(atspi_hyperlink_get_uri(ATSPI_HYPERLINK(link.get()), 0, nullptr));
+ g_assert_cmpstr(uri.get(), ==, "");
+ g_assert_true(atspi_hyperlink_get_object(ATSPI_HYPERLINK(link.get()), 0, nullptr) == button1.get());
+ auto button2 = adoptGRef(atspi_accessible_get_child_at_index(p.get(), 1, nullptr));
+ g_assert_true(ATSPI_IS_ACCESSIBLE(button2.get()));
+ link = adoptGRef(atspi_accessible_get_hyperlink(button2.get()));
+ g_assert_true(ATSPI_IS_HYPERLINK(link.get()));
+ g_assert_cmpint(atspi_hyperlink_get_n_anchors(ATSPI_HYPERLINK(link.get()), nullptr), ==, 1);
+ g_assert_true(atspi_hyperlink_is_valid(ATSPI_HYPERLINK(link.get()), nullptr));
+#if USE(ATSPI)
+ g_assert_cmpint(atspi_hyperlink_get_start_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 14);
+ g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 15);
+#else
+ g_assert_cmpint(atspi_hyperlink_get_start_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 21);
+ g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 29);
+#endif
+ uri.reset(atspi_hyperlink_get_uri(ATSPI_HYPERLINK(link.get()), 0, nullptr));
+ g_assert_cmpstr(uri.get(), ==, "");
+ g_assert_true(atspi_hyperlink_get_object(ATSPI_HYPERLINK(link.get()), 0, nullptr) == button2.get());
+
+ p = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 3, nullptr));
+ g_assert_true(ATSPI_IS_ACCESSIBLE(p.get()));
+ g_assert_cmpint(atspi_accessible_get_child_count(p.get(), nullptr), ==, 2);
+ auto link1 = adoptGRef(atspi_accessible_get_child_at_index(p.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_ACCESSIBLE(link1.get()));
+ link = adoptGRef(atspi_accessible_get_hyperlink(link1.get()));
+ g_assert_true(ATSPI_IS_HYPERLINK(link.get()));
+ g_assert_cmpint(atspi_hyperlink_get_n_anchors(ATSPI_HYPERLINK(link.get()), nullptr), ==, 1);
+ g_assert_true(atspi_hyperlink_is_valid(ATSPI_HYPERLINK(link.get()), nullptr));
+ g_assert_cmpint(atspi_hyperlink_get_start_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 8);
+#if USE(ATSPI)
+ g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 9);
+#else
+ g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 13);
+#endif
+ uri.reset(atspi_hyperlink_get_uri(ATSPI_HYPERLINK(link.get()), 0, nullptr));
+ g_assert_cmpstr(uri.get(), ==, "https://www.webkitgtk.org/");
+ g_assert_true(atspi_hyperlink_get_object(ATSPI_HYPERLINK(link.get()), 0, nullptr) == link1.get());
+ auto link2 = adoptGRef(atspi_accessible_get_child_at_index(p.get(), 1, nullptr));
+ g_assert_true(ATSPI_IS_ACCESSIBLE(link2.get()));
+ link = adoptGRef(atspi_accessible_get_hyperlink(link2.get()));
+ g_assert_true(ATSPI_IS_HYPERLINK(link.get()));
+ g_assert_cmpint(atspi_hyperlink_get_n_anchors(ATSPI_HYPERLINK(link.get()), nullptr), ==, 1);
+ g_assert_true(atspi_hyperlink_is_valid(ATSPI_HYPERLINK(link.get()), nullptr));
+#if USE(ATSPI)
+ g_assert_cmpint(atspi_hyperlink_get_start_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 14);
+ g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 15);
+#else
+ g_assert_cmpint(atspi_hyperlink_get_start_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 18);
+ g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 23);
+#endif
+ uri.reset(atspi_hyperlink_get_uri(ATSPI_HYPERLINK(link.get()), 0, nullptr));
+ g_assert_cmpstr(uri.get(), ==, "https://www.gnome.org/");
+ g_assert_true(atspi_hyperlink_get_object(ATSPI_HYPERLINK(link.get()), 0, nullptr) == link2.get());
+}
+
void beforeAll()
{
AccessibilityTest::add("WebKitAccessibility", "accessible/basic-hierarchy", testAccessibleBasicHierarchy);
@@ -1686,6 +1838,7 @@
AccessibilityTest::add("WebKitAccessibility", "text/state-changed", testTextStateChanged);
AccessibilityTest::add("WebKitAccessibility", "text/replaced-objects", testTextReplacedObjects);
AccessibilityTest::add("WebKitAccessibility", "value/basic", testValueBasic);
+ AccessibilityTest::add("WebKitAccessibility", "hyperlink/basic", testHyperlinkBasic);
}
void afterAll()