Title: [286416] trunk
Revision
286416
Author
[email protected]
Date
2021-12-02 00:16:13 -0800 (Thu, 02 Dec 2021)

Log Message

[GTK][a11y] Add implementation of table and table cell interfaces when building with ATSPI
https://bugs.webkit.org/show_bug.cgi?id=233694

Reviewed by Adrian Perez de Castro.

Source/WebCore:

* SourcesGTK.txt:
* accessibility/AccessibilityTableColumn.cpp:
(WebCore::AccessibilityTableColumn::computeAccessibilityIsIgnored const): Do not expose column for ATSPI.
* accessibility/AccessibilityTableHeaderContainer.cpp:
(WebCore::AccessibilityTableHeaderContainer::computeAccessibilityIsIgnored const): Do not expose header container either.
* accessibility/atspi/AccessibilityObjectAtspi.cpp:
(WebCore::AccessibilityObjectAtspi::interfacesForObject):
(WebCore::AccessibilityObjectAtspi::path):
(WebCore::AccessibilityObjectAtspi::wrapperVector const):
(WebCore::AccessibilityObjectAtspi::children const):
(WebCore::AccessibilityObjectAtspi::buildInterfaces const):
* accessibility/atspi/AccessibilityObjectAtspi.h:
* accessibility/atspi/AccessibilityObjectTableAtspi.cpp: Added.
(WebCore::AccessibilityObjectAtspi::rowCount const):
(WebCore::AccessibilityObjectAtspi::columnCount const):
(WebCore::AccessibilityObjectAtspi::cell const):
(WebCore::AccessibilityObjectAtspi::tableCaption const):
(WebCore::AccessibilityObjectAtspi::cellIndex const):
(WebCore::AccessibilityObjectAtspi::rowAtIndex const):
(WebCore::AccessibilityObjectAtspi::columnAtIndex const):
(WebCore::AccessibilityObjectAtspi::rowHeader const):
(WebCore::AccessibilityObjectAtspi::columnHeader const):
(WebCore::AccessibilityObjectAtspi::rowDescription const):
(WebCore::AccessibilityObjectAtspi::columnDescription const):
(WebCore::AccessibilityObjectAtspi::rowExtent const):
(WebCore::AccessibilityObjectAtspi::columnExtent const):
(WebCore::AccessibilityObjectAtspi::cells const):
(WebCore::AccessibilityObjectAtspi::rows const):
(WebCore::AccessibilityObjectAtspi::rowHeaders const):
(WebCore::AccessibilityObjectAtspi::columnHeaders const):
* accessibility/atspi/AccessibilityObjectTableCellAtspi.cpp: Added.
(WebCore::AccessibilityObjectAtspi::cellRowHeaders const):
(WebCore::AccessibilityObjectAtspi::cellColumnHeaders const):
(WebCore::AccessibilityObjectAtspi::rowSpan const):
(WebCore::AccessibilityObjectAtspi::columnSpan const):
(WebCore::AccessibilityObjectAtspi::cellPosition const):
* accessibility/atspi/xml/TableCell.xml:

Tools:

Add unit tests and WTR implementation for tables.

* TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp:
(testTableBasic):
(beforeAll):
* WebKitTestRunner/InjectedBundle/atspi/AccessibilityUIElementAtspi.cpp:
(WTR::AccessibilityUIElement::rowAtIndex):
(WTR::elementsVector):
(WTR::attributesOfElements):
(WTR::AccessibilityUIElement::attributesOfChildren):
(WTR::AccessibilityUIElement::numberAttributeValue):
(WTR::makeJSArray):
(WTR::AccessibilityUIElement::rowHeaders const):
(WTR::AccessibilityUIElement::columnHeaders const):
(WTR::AccessibilityUIElement::attributesOfColumnHeaders):
(WTR::AccessibilityUIElement::attributesOfRowHeaders):
(WTR::AccessibilityUIElement::attributesOfRows):
(WTR::AccessibilityUIElement::attributesOfVisibleCells):
(WTR::AccessibilityUIElement::rowCount):
(WTR::AccessibilityUIElement::columnCount):
(WTR::AccessibilityUIElement::rowIndexRange):
(WTR::AccessibilityUIElement::columnIndexRange):
(WTR::AccessibilityUIElement::cellForColumnAndRow):

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (286415 => 286416)


--- trunk/Source/WebCore/ChangeLog	2021-12-02 08:06:24 UTC (rev 286415)
+++ trunk/Source/WebCore/ChangeLog	2021-12-02 08:16:13 UTC (rev 286416)
@@ -1,3 +1,48 @@
+2021-12-01  Carlos Garcia Campos  <[email protected]>
+
+        [GTK][a11y] Add implementation of table and table cell interfaces when building with ATSPI
+        https://bugs.webkit.org/show_bug.cgi?id=233694
+
+        Reviewed by Adrian Perez de Castro.
+
+        * SourcesGTK.txt:
+        * accessibility/AccessibilityTableColumn.cpp:
+        (WebCore::AccessibilityTableColumn::computeAccessibilityIsIgnored const): Do not expose column for ATSPI.
+        * accessibility/AccessibilityTableHeaderContainer.cpp:
+        (WebCore::AccessibilityTableHeaderContainer::computeAccessibilityIsIgnored const): Do not expose header container either.
+        * accessibility/atspi/AccessibilityObjectAtspi.cpp:
+        (WebCore::AccessibilityObjectAtspi::interfacesForObject):
+        (WebCore::AccessibilityObjectAtspi::path):
+        (WebCore::AccessibilityObjectAtspi::wrapperVector const):
+        (WebCore::AccessibilityObjectAtspi::children const):
+        (WebCore::AccessibilityObjectAtspi::buildInterfaces const):
+        * accessibility/atspi/AccessibilityObjectAtspi.h:
+        * accessibility/atspi/AccessibilityObjectTableAtspi.cpp: Added.
+        (WebCore::AccessibilityObjectAtspi::rowCount const):
+        (WebCore::AccessibilityObjectAtspi::columnCount const):
+        (WebCore::AccessibilityObjectAtspi::cell const):
+        (WebCore::AccessibilityObjectAtspi::tableCaption const):
+        (WebCore::AccessibilityObjectAtspi::cellIndex const):
+        (WebCore::AccessibilityObjectAtspi::rowAtIndex const):
+        (WebCore::AccessibilityObjectAtspi::columnAtIndex const):
+        (WebCore::AccessibilityObjectAtspi::rowHeader const):
+        (WebCore::AccessibilityObjectAtspi::columnHeader const):
+        (WebCore::AccessibilityObjectAtspi::rowDescription const):
+        (WebCore::AccessibilityObjectAtspi::columnDescription const):
+        (WebCore::AccessibilityObjectAtspi::rowExtent const):
+        (WebCore::AccessibilityObjectAtspi::columnExtent const):
+        (WebCore::AccessibilityObjectAtspi::cells const):
+        (WebCore::AccessibilityObjectAtspi::rows const):
+        (WebCore::AccessibilityObjectAtspi::rowHeaders const):
+        (WebCore::AccessibilityObjectAtspi::columnHeaders const):
+        * accessibility/atspi/AccessibilityObjectTableCellAtspi.cpp: Added.
+        (WebCore::AccessibilityObjectAtspi::cellRowHeaders const):
+        (WebCore::AccessibilityObjectAtspi::cellColumnHeaders const):
+        (WebCore::AccessibilityObjectAtspi::rowSpan const):
+        (WebCore::AccessibilityObjectAtspi::columnSpan const):
+        (WebCore::AccessibilityObjectAtspi::cellPosition const):
+        * accessibility/atspi/xml/TableCell.xml:
+
 2021-12-02  Manuel Rego Casasnovas  <[email protected]>
 
         [selectors] :focus-visible should stop matching after blur

Modified: trunk/Source/WebCore/SourcesGTK.txt (286415 => 286416)


--- trunk/Source/WebCore/SourcesGTK.txt	2021-12-02 08:06:24 UTC (rev 286415)
+++ trunk/Source/WebCore/SourcesGTK.txt	2021-12-02 08:16:13 UTC (rev 286416)
@@ -48,6 +48,8 @@
 accessibility/atspi/AccessibilityObjectHypertextAtspi.cpp
 accessibility/atspi/AccessibilityObjectImageAtspi.cpp
 accessibility/atspi/AccessibilityObjectSelectionAtspi.cpp
+accessibility/atspi/AccessibilityObjectTableAtspi.cpp
+accessibility/atspi/AccessibilityObjectTableCellAtspi.cpp
 accessibility/atspi/AccessibilityObjectTextAtspi.cpp
 accessibility/atspi/AccessibilityObjectValueAtspi.cpp
 accessibility/atspi/AccessibilityRootAtspi.cpp

Modified: trunk/Source/WebCore/accessibility/AccessibilityTableColumn.cpp (286415 => 286416)


--- trunk/Source/WebCore/accessibility/AccessibilityTableColumn.cpp	2021-12-02 08:06:24 UTC (rev 286415)
+++ trunk/Source/WebCore/accessibility/AccessibilityTableColumn.cpp	2021-12-02 08:16:13 UTC (rev 286416)
@@ -172,7 +172,7 @@
     if (!m_parent)
         return true;
     
-#if PLATFORM(IOS_FAMILY) || USE(ATK)
+#if PLATFORM(IOS_FAMILY) || USE(ATK) || USE(ATSPI)
     return true;
 #endif
     

Modified: trunk/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp (286415 => 286416)


--- trunk/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp	2021-12-02 08:06:24 UTC (rev 286415)
+++ trunk/Source/WebCore/accessibility/AccessibilityTableHeaderContainer.cpp	2021-12-02 08:16:13 UTC (rev 286416)
@@ -54,7 +54,7 @@
     if (!m_parent)
         return true;
     
-#if PLATFORM(IOS_FAMILY) || USE(ATK)
+#if PLATFORM(IOS_FAMILY) || USE(ATK) || USE(ATSPI)
     return true;
 #endif
 

Modified: trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp (286415 => 286416)


--- trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp	2021-12-02 08:06:24 UTC (rev 286415)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp	2021-12-02 08:16:13 UTC (rev 286416)
@@ -100,6 +100,15 @@
     if (coreObject.canHaveSelectedChildren())
         interfaces.add(Interface::Selection);
 
+    if (coreObject.isTable())
+        interfaces.add(Interface::Table);
+
+    if (coreObject.roleValue() == AccessibilityRole::Cell
+        || coreObject.roleValue() == AccessibilityRole::GridCell
+        || coreObject.roleValue() == AccessibilityRole::ColumnHeader
+        || coreObject.roleValue() == AccessibilityRole::RowHeader)
+        interfaces.add(Interface::TableCell);
+
     return interfaces;
 }
 
@@ -500,6 +509,10 @@
             interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_image_interface), &s_imageFunctions });
         if (m_interfaces.contains(Interface::Selection))
             interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_selection_interface), &s_selectionFunctions });
+        if (m_interfaces.contains(Interface::Table))
+            interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_table_interface), &s_tableFunctions });
+        if (m_interfaces.contains(Interface::TableCell))
+            interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_table_cell_interface), &s_tableCellFunctions });
         m_path = atspiRoot->atspi().registerObject(*this, WTFMove(interfaces));
     }
 
@@ -556,8 +569,10 @@
     if (!axParent)
         return nullptr;
 
-    if (auto* atspiParent = axParent->wrapper())
+    if (auto* atspiParent = axParent->wrapper()) {
+        atspiParent->setRoot(root());
         return atspiParent;
+    }
 
     return std::nullopt;
 }
@@ -598,18 +613,13 @@
     return wrapper;
 }
 
-Vector<RefPtr<AccessibilityObjectAtspi>> AccessibilityObjectAtspi::children() const
+Vector<RefPtr<AccessibilityObjectAtspi>> AccessibilityObjectAtspi::wrapperVector(const Vector<RefPtr<AXCoreObject>>& elements) const
 {
-    RELEASE_ASSERT(!isMainThread());
-    if (!m_axObject)
-        return { };
-
-    const auto& children = m_axObject->children();
     Vector<RefPtr<AccessibilityObjectAtspi>> wrappers;
-    wrappers.reserveInitialCapacity(children.size());
+    wrappers.reserveInitialCapacity(elements.size());
     auto* root = this->root();
-    for (const auto& child : children) {
-        if (auto* wrapper = child->wrapper()) {
+    for (const auto& element : elements) {
+        if (auto* wrapper = element->wrapper()) {
             wrapper->setRoot(root);
             wrappers.uncheckedAppend(wrapper);
         }
@@ -617,6 +627,15 @@
     return wrappers;
 }
 
+Vector<RefPtr<AccessibilityObjectAtspi>> AccessibilityObjectAtspi::children() const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return { };
+
+    return wrapperVector(m_axObject->children());
+}
+
 int AccessibilityObjectAtspi::indexInParent() const
 {
     RELEASE_ASSERT(!isMainThread());
@@ -1152,6 +1171,10 @@
         g_variant_builder_add(builder, "s", webkit_image_interface.name);
     if (m_interfaces.contains(Interface::Selection))
         g_variant_builder_add(builder, "s", webkit_selection_interface.name);
+    if (m_interfaces.contains(Interface::Table))
+        g_variant_builder_add(builder, "s", webkit_table_interface.name);
+    if (m_interfaces.contains(Interface::TableCell))
+        g_variant_builder_add(builder, "s", webkit_table_cell_interface.name);
 }
 
 void AccessibilityObjectAtspi::serialize(GVariantBuilder* builder) const

Modified: trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.h (286415 => 286416)


--- trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.h	2021-12-02 08:06:24 UTC (rev 286415)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.h	2021-12-02 08:16:13 UTC (rev 286416)
@@ -52,7 +52,9 @@
         Action = 1 << 6,
         Document = 1 << 7,
         Image = 1 << 8,
-        Selection = 1 << 9
+        Selection = 1 << 9,
+        Table = 1 << 10,
+        TableCell = 1 << 11
     };
     const OptionSet<Interface>& interfaces() const { return m_interfaces; }
 
@@ -138,9 +140,24 @@
     WEBCORE_EXPORT bool clearSelection() const;
     void selectionChanged();
 
+    WEBCORE_EXPORT AccessibilityObjectAtspi* cell(unsigned row, unsigned column) const;
+    WEBCORE_EXPORT unsigned rowCount() const;
+    WEBCORE_EXPORT unsigned columnCount() const;
+    WEBCORE_EXPORT Vector<RefPtr<AccessibilityObjectAtspi>> cells() const;
+    WEBCORE_EXPORT Vector<RefPtr<AccessibilityObjectAtspi>> rows() const;
+    WEBCORE_EXPORT Vector<RefPtr<AccessibilityObjectAtspi>> rowHeaders() const;
+    WEBCORE_EXPORT Vector<RefPtr<AccessibilityObjectAtspi>> columnHeaders() const;
+
+    WEBCORE_EXPORT Vector<RefPtr<AccessibilityObjectAtspi>> cellRowHeaders() const;
+    WEBCORE_EXPORT Vector<RefPtr<AccessibilityObjectAtspi>> cellColumnHeaders() const;
+    WEBCORE_EXPORT unsigned rowSpan() const;
+    WEBCORE_EXPORT unsigned columnSpan() const;
+    WEBCORE_EXPORT std::pair<std::optional<unsigned>, std::optional<unsigned>> cellPosition() const;
+
 private:
     explicit AccessibilityObjectAtspi(AXCoreObject*);
 
+    Vector<RefPtr<AccessibilityObjectAtspi>> wrapperVector(const Vector<RefPtr<AXCoreObject>>&) const;
     int indexInParent() const;
     GVariant* parentReference() const;
     void childAdded(AccessibilityObjectAtspi&);
@@ -189,6 +206,18 @@
     bool isChildSelected(unsigned) const;
     bool selectAll() const;
 
+    AccessibilityObjectAtspi* rowHeader(unsigned) const;
+    AccessibilityObjectAtspi* columnHeader(unsigned) const;
+    unsigned rowExtent(unsigned row, unsigned column) const;
+    unsigned columnExtent(unsigned row, unsigned column) const;
+
+    AccessibilityObjectAtspi* tableCaption() const;
+    std::optional<unsigned> cellIndex(unsigned row, unsigned column) const;
+    std::optional<unsigned> rowAtIndex(unsigned) const;
+    std::optional<unsigned> columnAtIndex(unsigned) const;
+    String rowDescription(unsigned) const;
+    String columnDescription(unsigned) const;
+
     static OptionSet<Interface> interfacesForObject(AXCoreObject&);
 
     static GDBusInterfaceVTable s_accessibleFunctions;
@@ -201,6 +230,8 @@
     static GDBusInterfaceVTable s_documentFunctions;
     static GDBusInterfaceVTable s_imageFunctions;
     static GDBusInterfaceVTable s_selectionFunctions;
+    static GDBusInterfaceVTable s_tableFunctions;
+    static GDBusInterfaceVTable s_tableCellFunctions;
 
     AXCoreObject* m_axObject { nullptr };
     AXCoreObject* m_coreObject { nullptr };

Added: trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTableAtspi.cpp (0 => 286416)


--- trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTableAtspi.cpp	                        (rev 0)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTableAtspi.cpp	2021-12-02 08:16:13 UTC (rev 286416)
@@ -0,0 +1,406 @@
+/*
+ * 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 "HTMLTableCaptionElement.h"
+#include "HTMLTableElement.h"
+#include "RenderElement.h"
+#include <gio/gio.h>
+#include <wtf/text/StringBuilder.h>
+
+namespace WebCore {
+
+GDBusInterfaceVTable AccessibilityObjectAtspi::s_tableFunctions = {
+    // 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, "GetAccessibleAt")) {
+            int row, column;
+            g_variant_get(parameters, "(ii)", &row, &column);
+            auto* cell = row >= 0 && column >= 0 ? atspiObject->cell(row, column) : nullptr;
+            g_dbus_method_invocation_return_value(invocation, g_variant_new("(@(so))", cell ? cell->reference() : atspiObject->root()->atspi().nullReference()));
+        } else if (!g_strcmp0(methodName, "GetIndexAt")) {
+            int row, column;
+            g_variant_get(parameters, "(ii)", &row, &column);
+            g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", row >= 0 && column >= 0 ? atspiObject->cellIndex(row, column).value_or(-1) : -1));
+        } else if (!g_strcmp0(methodName, "GetRowAtIndex")) {
+            int index;
+            g_variant_get(parameters, "(i)", &index);
+            g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", index >= 0 ? atspiObject->rowAtIndex(index).value_or(-1) : -1));
+        } else if (!g_strcmp0(methodName, "GetColumnAtIndex")) {
+            int index;
+            g_variant_get(parameters, "(i)", &index);
+            g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", index >= 0 ? atspiObject->columnAtIndex(index).value_or(-1) : -1));
+        } else if (!g_strcmp0(methodName, "GetRowDescription")) {
+            int index;
+            g_variant_get(parameters, "(i)", &index);
+            g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", index >= 0 ? atspiObject->rowDescription(index).utf8().data() : ""));
+        } else if (!g_strcmp0(methodName, "GetColumnDescription")) {
+            int index;
+            g_variant_get(parameters, "(i)", &index);
+            g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", index >= 0 ? atspiObject->columnDescription(index).utf8().data() : ""));
+        } else if (!g_strcmp0(methodName, "GetRowExtentAt")) {
+            int row, column;
+            g_variant_get(parameters, "(ii)", &row, &column);
+            g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", row >= 0 && column >= 0 ? atspiObject->rowExtent(row, column) : -1));
+        } else if (!g_strcmp0(methodName, "GetColumnExtentAt")) {
+            int row, column;
+            g_variant_get(parameters, "(ii)", &row, &column);
+            g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", row >= 0 && column >= 0 ? atspiObject->columnExtent(row, column) : -1));
+        } else if (!g_strcmp0(methodName, "GetRowHeader")) {
+            int row;
+            g_variant_get(parameters, "(i)", &row);
+            auto* header = row >= 0 ? atspiObject->rowHeader(row) : nullptr;
+            g_dbus_method_invocation_return_value(invocation, g_variant_new("(@(so))", header ? header->reference() : atspiObject->root()->atspi().nullReference()));
+        } else if (!g_strcmp0(methodName, "GetColumnHeader")) {
+            int column;
+            g_variant_get(parameters, "(i)", &column);
+            auto* header = column >= 0 ? atspiObject->columnHeader(column) : nullptr;
+            g_dbus_method_invocation_return_value(invocation, g_variant_new("(@(so))", header ? header->reference() : atspiObject->root()->atspi().nullReference()));
+        } else if (!g_strcmp0(methodName, "GetRowColumnExtentsAtIndex")) {
+            int index;
+            g_variant_get(parameters, "(i)", &index);
+            if (index < 0) {
+                g_dbus_method_invocation_return_value(invocation, g_variant_new("(biiiib)", FALSE, -1, -1, -1, -1, FALSE));
+                return;
+            }
+
+            auto row = atspiObject->rowAtIndex(index);
+            auto column = atspiObject->columnAtIndex(index);
+            auto* cell = atspiObject->m_axObject ? atspiObject->m_axObject->cellForColumnAndRow(*column, *row) : nullptr;
+            g_dbus_method_invocation_return_value(invocation, g_variant_new("(biiiib)", cell && cell->isTableCell() ? TRUE : FALSE,
+                row.value_or(-1), column.value_or(-1), row && column ? atspiObject->rowExtent(*row, *column) : -1,
+                row && column ? atspiObject->columnExtent(*row, *column) : -1, FALSE));
+        } else if (!g_strcmp0(methodName, "GetSelectedRows")
+            || !g_strcmp0(methodName, "GetSelectedColumns")
+            || !g_strcmp0(methodName, "IsRowSelected")
+            || !g_strcmp0(methodName, "IsColumnSelected")
+            || !g_strcmp0(methodName, "IsSelected")
+            || !g_strcmp0(methodName, "AddRowSelection")
+            || !g_strcmp0(methodName, "AddColumnSelection")
+            || !g_strcmp0(methodName, "RemoveRowSelection")
+            || !g_strcmp0(methodName, "RemoveColumnSelection"))
+            g_dbus_method_invocation_return_error_literal(invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+    },
+    // 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, "NRows"))
+            return g_variant_new_int32(atspiObject->rowCount());
+        if (!g_strcmp0(propertyName, "NColumns"))
+            return g_variant_new_int32(atspiObject->columnCount());
+        if (!g_strcmp0(propertyName, "Caption")) {
+            auto* caption = atspiObject->tableCaption();
+            return caption ? caption->reference() : atspiObject->root()->atspi().nullReference();
+        }
+        if (!g_strcmp0(propertyName, "Summary"))
+            return atspiObject->root()->atspi().nullReference();
+        if (!g_strcmp0(propertyName, "NSelectedRows"))
+            return g_variant_new_int32(0);
+        if (!g_strcmp0(propertyName, "NSelectedColumns"))
+            return g_variant_new_int32(0);
+
+        g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unknown property '%s'", propertyName);
+        return nullptr;
+    },
+    // set_property,
+    nullptr,
+    // padding
+    nullptr
+};
+
+unsigned AccessibilityObjectAtspi::rowCount() const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return 0;
+
+    return m_axObject->rowCount();
+}
+
+unsigned AccessibilityObjectAtspi::columnCount() const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return 0;
+
+    return m_axObject->columnCount();
+}
+
+AccessibilityObjectAtspi* AccessibilityObjectAtspi::cell(unsigned row, unsigned column) const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return nullptr;
+
+    if (auto* tableCell = m_axObject->cellForColumnAndRow(column, row)) {
+        if (auto* wrapper = tableCell->wrapper()) {
+            wrapper->setRoot(root());
+            return wrapper;
+        }
+    }
+
+    return nullptr;
+}
+
+AccessibilityObjectAtspi* AccessibilityObjectAtspi::tableCaption() const
+{
+    return Accessibility::retrieveValueFromMainThread<AccessibilityObjectAtspi*>([this]() -> AccessibilityObjectAtspi* {
+        if (m_coreObject)
+            m_coreObject->updateBackingStore();
+
+        if (!m_coreObject)
+            return nullptr;
+
+        if (auto* node = m_coreObject->node()) {
+            if (!is<HTMLTableElement>(node))
+                return nullptr;
+
+            if (auto caption = downcast<HTMLTableElement>(*node).caption()) {
+                if (auto* renderer = caption->renderer()) {
+                    if (auto* element = AccessibilityObject::firstAccessibleObjectFromNode(renderer->element())) {
+                        if (auto* wrapper = element->wrapper()) {
+                            wrapper->setRoot(root());
+                            return wrapper;
+                        }
+                    }
+                }
+            }
+        }
+
+        return nullptr;
+    });
+}
+
+std::optional<unsigned> AccessibilityObjectAtspi::cellIndex(unsigned row, unsigned column) const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return std::nullopt;
+
+    auto* cell = m_axObject->cellForColumnAndRow(column, row);
+    if (!cell)
+        return std::nullopt;
+
+    auto cells = m_axObject->cells();
+    AXCoreObject::AccessibilityChildrenVector::iterator position;
+    position = std::find(cells.begin(), cells.end(), cell);
+    if (position == cells.end())
+        return std::nullopt;
+    return position - cells.begin();
+}
+
+std::optional<unsigned> AccessibilityObjectAtspi::rowAtIndex(unsigned index) const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return std::nullopt;
+
+    auto cells = m_axObject->cells();
+    if (index >= cells.size())
+        return std::nullopt;
+
+    return cells[index]->rowIndexRange().first;
+}
+
+std::optional<unsigned> AccessibilityObjectAtspi::columnAtIndex(unsigned index) const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return std::nullopt;
+
+    auto cells = m_axObject->cells();
+    if (index >= cells.size())
+        return std::nullopt;
+
+    return cells[index]->columnIndexRange().first;
+}
+
+AccessibilityObjectAtspi* AccessibilityObjectAtspi::rowHeader(unsigned row) const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return nullptr;
+
+    auto headers = m_axObject->rowHeaders();
+    for (const auto& header : headers) {
+        auto range = header->rowIndexRange();
+        if (range.first <= row && row < range.first + range.second) {
+            if (auto* wrapper = header->wrapper()) {
+                wrapper->setRoot(root());
+                return wrapper;
+            }
+        }
+    }
+
+    return nullptr;
+}
+
+AccessibilityObjectAtspi* AccessibilityObjectAtspi::columnHeader(unsigned column) const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return nullptr;
+
+    auto headers = m_axObject->columnHeaders();
+    for (const auto& header : headers) {
+        auto range = header->columnIndexRange();
+        if (range.first <= column && column < range.first + range.second) {
+            if (auto* wrapper = header->wrapper()) {
+                wrapper->setRoot(root());
+                return wrapper;
+            }
+        }
+    }
+
+    return nullptr;
+}
+
+String AccessibilityObjectAtspi::rowDescription(unsigned row) const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return { };
+
+    StringBuilder builder;
+    bool isFirst = true;
+    auto headers = m_axObject->rowHeaders();
+    for (const auto& header : headers) {
+        auto* wrapper = header->wrapper();
+        if (!wrapper)
+            continue;
+
+        auto range = header->rowIndexRange();
+        if (range.first <= row && row < range.first + range.second) {
+            wrapper->updateBackingStore();
+            auto text = wrapper->text();
+            if (text.isEmpty())
+                continue;
+
+            if (!isFirst)
+                builder.append(' ');
+            else
+                isFirst = false;
+            builder.append(text);
+        }
+    }
+    return builder.toString();
+}
+
+String AccessibilityObjectAtspi::columnDescription(unsigned column) const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return { };
+
+    StringBuilder builder;
+    bool isFirst = true;
+    auto headers = m_axObject->columnHeaders();
+    for (const auto& header : headers) {
+        auto* wrapper = header->wrapper();
+        if (!wrapper)
+            continue;
+
+        auto range = header->columnIndexRange();
+        if (range.first <= column && column < range.first + range.second) {
+            wrapper->updateBackingStore();
+            auto text = wrapper->text();
+            if (text.isEmpty())
+                continue;
+
+            if (!isFirst)
+                builder.append(' ');
+            else
+                isFirst = false;
+            builder.append(text);
+        }
+    }
+    return builder.toString();
+}
+
+unsigned AccessibilityObjectAtspi::rowExtent(unsigned row, unsigned column) const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return 0;
+
+    auto* cell = m_axObject->cellForColumnAndRow(column, row);
+    return cell ? cell->rowIndexRange().second : 0;
+}
+
+unsigned AccessibilityObjectAtspi::columnExtent(unsigned row, unsigned column) const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return 0;
+
+    auto* cell = m_axObject->cellForColumnAndRow(column, row);
+    return cell ? cell->columnIndexRange().second : 0;
+}
+
+Vector<RefPtr<AccessibilityObjectAtspi>> AccessibilityObjectAtspi::cells() const
+{
+    RELEASE_ASSERT(isMainThread());
+    if (!m_coreObject)
+        return { };
+
+    return wrapperVector(m_coreObject->cells());
+}
+
+Vector<RefPtr<AccessibilityObjectAtspi>> AccessibilityObjectAtspi::rows() const
+{
+    RELEASE_ASSERT(isMainThread());
+    if (!m_coreObject)
+        return { };
+
+    return wrapperVector(m_coreObject->rows());
+}
+
+Vector<RefPtr<AccessibilityObjectAtspi>> AccessibilityObjectAtspi::rowHeaders() const
+{
+    RELEASE_ASSERT(isMainThread());
+    if (!m_coreObject)
+        return { };
+
+    return wrapperVector(m_coreObject->rowHeaders());
+}
+
+Vector<RefPtr<AccessibilityObjectAtspi>> AccessibilityObjectAtspi::columnHeaders() const
+{
+    RELEASE_ASSERT(isMainThread());
+    if (!m_coreObject)
+        return { };
+
+    return wrapperVector(m_coreObject->columnHeaders());
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(ACCESSIBILITY) && USE(ATSPI)

Added: trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTableCellAtspi.cpp (0 => 286416)


--- trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTableCellAtspi.cpp	                        (rev 0)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTableCellAtspi.cpp	2021-12-02 08:16:13 UTC (rev 286416)
@@ -0,0 +1,174 @@
+/*
+ * 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 "AccessibilityAtspiEnums.h"
+#include "AccessibilityRootAtspi.h"
+#include <gio/gio.h>
+
+namespace WebCore {
+
+GDBusInterfaceVTable AccessibilityObjectAtspi::s_tableCellFunctions = {
+    // method_call
+    [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* methodName, GVariant*, GDBusMethodInvocation* invocation, gpointer userData) {
+        RELEASE_ASSERT(!isMainThread());
+        auto atspiObject = Ref { *static_cast<AccessibilityObjectAtspi*>(userData) };
+        atspiObject->updateBackingStore();
+
+        if (!g_strcmp0(methodName, "GetRowHeaderCells")) {
+            GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("a(so)"));
+            for (const auto& wrapper : atspiObject->cellRowHeaders())
+                g_variant_builder_add(&builder, "@(so)", wrapper->reference());
+            g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(so))", &builder));
+        } else if (!g_strcmp0(methodName, "GetColumnHeaderCells")) {
+            GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("a(so)"));
+            for (const auto& wrapper : atspiObject->cellColumnHeaders())
+                g_variant_builder_add(&builder, "@(so)", wrapper->reference());
+            g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(so))", &builder));
+        } else if (!g_strcmp0(methodName, "GetRowColumnSpan")) {
+            auto position = atspiObject->cellPosition();
+            g_dbus_method_invocation_return_value(invocation, g_variant_new("(iiii)", position.first.value_or(-1), position.second.value_or(-1), atspiObject->rowSpan(), atspiObject->columnSpan()));
+        }
+    },
+    // 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, "ColumnSpan"))
+            return g_variant_new_int32(atspiObject->columnSpan());
+        if (!g_strcmp0(propertyName, "Position")) {
+            auto position = atspiObject->cellPosition();
+            return g_variant_new("(ii)", position.first.value_or(-1), position.second.value_or(-1));
+        }
+        if (!g_strcmp0(propertyName, "RowSpan"))
+            return g_variant_new_int32(atspiObject->rowSpan());
+        if (!g_strcmp0(propertyName, "Table")) {
+            auto* axObject = atspiObject->m_axObject;
+            if (!axObject || !axObject->isTableCell())
+                return atspiObject->root()->atspi().nullReference();
+
+            AccessibilityObjectAtspi* wrapper = atspiObject.ptr();
+            while (auto parent = wrapper->parent()) {
+                wrapper = parent.value();
+                if (!wrapper)
+                    break;
+
+                wrapper->updateBackingStore();
+                axObject = wrapper->m_axObject;
+                if (axObject && axObject->isTable())
+                    break;
+            }
+            return wrapper ? wrapper->reference() : atspiObject->root()->atspi().nullReference();
+        }
+
+        g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unknown property '%s'", propertyName);
+        return nullptr;
+    },
+    // set_property,
+    nullptr,
+    // padding
+    nullptr
+};
+
+Vector<RefPtr<AccessibilityObjectAtspi>> AccessibilityObjectAtspi::cellRowHeaders() const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return { };
+
+    // Only return headers for cells that are not headers.
+    if (role() != Atspi::Role::TableCell)
+        return { };
+
+    auto headers = m_axObject->rowHeaders();
+    Vector<RefPtr<AccessibilityObjectAtspi>> wrappers;
+    wrappers.reserveInitialCapacity(headers.size());
+    auto* root = this->root();
+    for (const auto& header : headers) {
+        if (auto* wrapper = header->wrapper()) {
+            wrapper->setRoot(root);
+            wrappers.uncheckedAppend(wrapper);
+        }
+    }
+    return wrappers;
+}
+
+Vector<RefPtr<AccessibilityObjectAtspi>> AccessibilityObjectAtspi::cellColumnHeaders() const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return { };
+
+    // Only return headers for cells that are not headers.
+    if (role() != Atspi::Role::TableCell)
+        return { };
+
+    auto headers = m_axObject->columnHeaders();
+    Vector<RefPtr<AccessibilityObjectAtspi>> wrappers;
+    wrappers.reserveInitialCapacity(headers.size());
+    auto* root = this->root();
+    for (const auto& header : headers) {
+        if (auto* wrapper = header->wrapper()) {
+            wrapper->setRoot(root);
+            wrappers.uncheckedAppend(wrapper);
+        }
+    }
+    return wrappers;
+}
+
+unsigned AccessibilityObjectAtspi::rowSpan() const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return 0;
+
+    return m_axObject->rowIndexRange().second;
+}
+
+unsigned AccessibilityObjectAtspi::columnSpan() const
+{
+    RELEASE_ASSERT(!isMainThread());
+    if (!m_axObject)
+        return 0;
+
+    return m_axObject->columnIndexRange().second;
+}
+
+std::pair<std::optional<unsigned>, std::optional<unsigned>> AccessibilityObjectAtspi::cellPosition() const
+{
+    RELEASE_ASSERT(!isMainThread());
+    std::pair<std::optional<unsigned>, std::optional<unsigned>> position;
+    if (!m_axObject)
+        return position;
+
+    position.first = m_axObject->rowIndexRange().first;
+    position.second = m_axObject->columnIndexRange().first;
+
+    return position;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(ACCESSIBILITY) && USE(ATSPI)

Modified: trunk/Source/WebCore/accessibility/atspi/xml/TableCell.xml (286415 => 286416)


--- trunk/Source/WebCore/accessibility/atspi/xml/TableCell.xml	2021-12-02 08:06:24 UTC (rev 286415)
+++ trunk/Source/WebCore/accessibility/atspi/xml/TableCell.xml	2021-12-02 08:16:13 UTC (rev 286416)
@@ -14,8 +14,17 @@
     <annotation name="org.qtproject.QtDBus.QtTypeName" value="QSpiObjectReference"/>
   </property>
 
+  <method name="GetRowHeaderCells">
+    <arg direction="out" type="a(so)"/>
+    <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiObjectReferenceArray"/>
+  </method>
+
+  <method name="GetColumnHeaderCells">
+    <arg direction="out" type="a(so)"/>
+    <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QSpiObjectReferenceArray"/>
+  </method>
+
   <method name="GetRowColumnSpan">
-    <arg direction="out" type="b" />
     <arg direction="out" name="row" type="i" />
     <arg direction="out" name="col" type="i" />
     <arg direction="out" name="row_extents" type="i" />

Modified: trunk/Tools/ChangeLog (286415 => 286416)


--- trunk/Tools/ChangeLog	2021-12-02 08:06:24 UTC (rev 286415)
+++ trunk/Tools/ChangeLog	2021-12-02 08:16:13 UTC (rev 286416)
@@ -1,3 +1,34 @@
+2021-12-01  Carlos Garcia Campos  <[email protected]>
+
+        [GTK][a11y] Add implementation of table and table cell interfaces when building with ATSPI
+        https://bugs.webkit.org/show_bug.cgi?id=233694
+
+        Reviewed by Adrian Perez de Castro.
+
+        Add unit tests and WTR implementation for tables.
+
+        * TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp:
+        (testTableBasic):
+        (beforeAll):
+        * WebKitTestRunner/InjectedBundle/atspi/AccessibilityUIElementAtspi.cpp:
+        (WTR::AccessibilityUIElement::rowAtIndex):
+        (WTR::elementsVector):
+        (WTR::attributesOfElements):
+        (WTR::AccessibilityUIElement::attributesOfChildren):
+        (WTR::AccessibilityUIElement::numberAttributeValue):
+        (WTR::makeJSArray):
+        (WTR::AccessibilityUIElement::rowHeaders const):
+        (WTR::AccessibilityUIElement::columnHeaders const):
+        (WTR::AccessibilityUIElement::attributesOfColumnHeaders):
+        (WTR::AccessibilityUIElement::attributesOfRowHeaders):
+        (WTR::AccessibilityUIElement::attributesOfRows):
+        (WTR::AccessibilityUIElement::attributesOfVisibleCells):
+        (WTR::AccessibilityUIElement::rowCount):
+        (WTR::AccessibilityUIElement::columnCount):
+        (WTR::AccessibilityUIElement::rowIndexRange):
+        (WTR::AccessibilityUIElement::columnIndexRange):
+        (WTR::AccessibilityUIElement::cellForColumnAndRow):
+
 2021-12-01  Sihui Liu  <[email protected]>
 
         FileSystemSyncAccessHandle should be invalidated when network process crashes

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp (286415 => 286416)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp	2021-12-02 08:06:24 UTC (rev 286415)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp	2021-12-02 08:16:13 UTC (rev 286416)
@@ -2332,6 +2332,418 @@
 #endif
 }
 
+static void testTableBasic(AccessibilityTest* test, gconstpointer)
+{
+#if !USE(ATSPI)
+    g_test_skip("Tables work differently with ATK");
+#else
+    test->showInWindow(800, 600);
+    test->loadHtml(
+        "<html>"
+        "  <body>"
+        "    <table>"
+        "      <caption>Table Caption</caption>"
+        "      <tr><th>Column 1</th><th>Column 2</th><th>Column 3</th></tr>"
+        "      <tr><th rowspan='2'>Row 1 Cell 1</th><td>Row 1 Cell 2</td><td>Row 1 Cell 3</td></tr>"
+        "      <tr><td>Row 2 Cell 2</td><td>Row 2 Cell 3</td></tr>"
+        "      <tr><td colspan='3'>Row 3 Cell 1</td></tr>"
+        "    </table>"
+        "  </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), ==, 1);
+
+    auto table = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_TABLE(table.get()));
+    g_assert_cmpint(atspi_accessible_get_role(table.get(), nullptr), ==, ATSPI_ROLE_TABLE);
+    g_assert_cmpint(atspi_accessible_get_child_count(table.get(), nullptr), ==, 5);
+    g_assert_cmpint(atspi_table_get_n_rows(ATSPI_TABLE(table.get()), nullptr), ==, 4);
+    g_assert_cmpint(atspi_table_get_n_columns(ATSPI_TABLE(table.get()), nullptr), ==, 3);
+
+    auto caption = adoptGRef(atspi_table_get_caption(ATSPI_TABLE(table.get()), nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(caption.get()));
+    g_assert_cmpint(atspi_accessible_get_role(caption.get(), nullptr), ==, ATSPI_ROLE_CAPTION);
+    g_assert_true(ATSPI_IS_TEXT(caption.get()));
+    GUniquePtr<char> text(atspi_text_get_text(ATSPI_TEXT(caption.get()), 0, -1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Table Caption");
+
+    text.reset(atspi_table_get_row_description(ATSPI_TABLE(table.get()), 0, nullptr));
+    g_assert_cmpstr(text.get(), ==, "");
+    g_assert_null(atspi_table_get_row_header(ATSPI_TABLE(table.get()), 0, nullptr));
+    text.reset(atspi_table_get_row_description(ATSPI_TABLE(table.get()), 1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Row 1 Cell 1");
+    auto rowHeader = adoptGRef(atspi_table_get_row_header(ATSPI_TABLE(table.get()), 1, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(rowHeader.get()));
+    text.reset(atspi_table_get_row_description(ATSPI_TABLE(table.get()), 2, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Row 1 Cell 1");
+    auto header = adoptGRef(atspi_table_get_row_header(ATSPI_TABLE(table.get()), 2, nullptr));
+    g_assert_true(rowHeader.get() == header.get());
+    text.reset(atspi_table_get_row_description(ATSPI_TABLE(table.get()), 3, nullptr));
+    g_assert_cmpstr(text.get(), ==, "");
+    g_assert_null(atspi_table_get_row_header(ATSPI_TABLE(table.get()), 3, nullptr));
+
+    text.reset(atspi_table_get_column_description(ATSPI_TABLE(table.get()), 0, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Column 1");
+    auto columnHeader0 = adoptGRef(atspi_table_get_column_header(ATSPI_TABLE(table.get()), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(columnHeader0.get()));
+    text.reset(atspi_table_get_column_description(ATSPI_TABLE(table.get()), 1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Column 2");
+    auto columnHeader1 = adoptGRef(atspi_table_get_column_header(ATSPI_TABLE(table.get()), 1, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(columnHeader1.get()));
+    text.reset(atspi_table_get_column_description(ATSPI_TABLE(table.get()), 2, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Column 3");
+    auto columnHeader2 = adoptGRef(atspi_table_get_column_header(ATSPI_TABLE(table.get()), 2, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(columnHeader2.get()));
+
+    g_assert_cmpint(atspi_table_get_index_at(ATSPI_TABLE(table.get()), 0, 0, nullptr), ==, 0);
+    g_assert_cmpint(atspi_table_get_row_at_index(ATSPI_TABLE(table.get()), 0, nullptr), ==, 0);
+    g_assert_cmpint(atspi_table_get_column_at_index(ATSPI_TABLE(table.get()), 0, nullptr), ==, 0);
+    g_assert_cmpint(atspi_table_get_row_extent_at(ATSPI_TABLE(table.get()), 0, 0, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_column_extent_at(ATSPI_TABLE(table.get()), 0, 0, nullptr), ==, 1);
+    int row, column, rowSpan, columnSpan;
+    gboolean isSelected;
+    g_assert_true(atspi_table_get_row_column_extents_at_index(ATSPI_TABLE(table.get()), 0, &row, &column, &rowSpan, &columnSpan, &isSelected, nullptr));
+    g_assert_cmpint(row, ==, 0);
+    g_assert_cmpint(column, ==, 0);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 1);
+    g_assert_false(isSelected);
+    auto cell0 = adoptGRef(atspi_table_get_accessible_at(ATSPI_TABLE(table.get()), 0, 0, nullptr));
+    g_assert_true(ATSPI_IS_TABLE_CELL(cell0.get()));
+    g_assert_true(cell0.get() == columnHeader0.get());
+    g_assert_cmpint(atspi_table_cell_get_row_span(ATSPI_TABLE_CELL(cell0.get()), nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_cell_get_column_span(ATSPI_TABLE_CELL(cell0.get()), nullptr), ==, 1);
+    g_assert_true(atspi_table_cell_get_position(ATSPI_TABLE_CELL(cell0.get()), &row, &column, nullptr));
+    g_assert_cmpint(row, ==, 0);
+    g_assert_cmpint(column, ==, 0);
+    atspi_table_cell_get_row_column_span(ATSPI_TABLE_CELL(cell0.get()), &row, &column, &rowSpan, &columnSpan, nullptr);
+    g_assert_cmpint(row, ==, 0);
+    g_assert_cmpint(column, ==, 0);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 1);
+    auto cellTable = adoptGRef(atspi_table_cell_get_table(ATSPI_TABLE_CELL(cell0.get()), nullptr));
+    g_assert_true(table.get() == cellTable.get());
+    GRefPtr<GPtrArray> rowHeaders = adoptGRef(atspi_table_cell_get_row_header_cells(ATSPI_TABLE_CELL(cell0.get()), nullptr));
+    g_assert_cmpint(rowHeaders->len, ==, 0);
+    GRefPtr<GPtrArray> columnHeaders = adoptGRef(atspi_table_cell_get_column_header_cells(ATSPI_TABLE_CELL(cell0.get()), nullptr));
+    g_assert_cmpint(columnHeaders->len, ==, 0);
+    g_assert_true(ATSPI_IS_TEXT(cell0.get()));
+    text.reset(atspi_text_get_text(ATSPI_TEXT(cell0.get()), 0, -1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Column 1");
+
+    g_assert_cmpint(atspi_table_get_index_at(ATSPI_TABLE(table.get()), 0, 1, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_row_at_index(ATSPI_TABLE(table.get()), 1, nullptr), ==, 0);
+    g_assert_cmpint(atspi_table_get_column_at_index(ATSPI_TABLE(table.get()), 1, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_row_extent_at(ATSPI_TABLE(table.get()), 0, 1, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_column_extent_at(ATSPI_TABLE(table.get()), 0, 1, nullptr), ==, 1);
+    g_assert_true(atspi_table_get_row_column_extents_at_index(ATSPI_TABLE(table.get()), 1, &row, &column, &rowSpan, &columnSpan, &isSelected, nullptr));
+    g_assert_cmpint(row, ==, 0);
+    g_assert_cmpint(column, ==, 1);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 1);
+    g_assert_false(isSelected);
+    auto cell1 = adoptGRef(atspi_table_get_accessible_at(ATSPI_TABLE(table.get()), 0, 1, nullptr));
+    g_assert_true(ATSPI_IS_TABLE_CELL(cell1.get()));
+    g_assert_true(cell1.get() == columnHeader1.get());
+    g_assert_cmpint(atspi_table_cell_get_row_span(ATSPI_TABLE_CELL(cell1.get()), nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_cell_get_column_span(ATSPI_TABLE_CELL(cell1.get()), nullptr), ==, 1);
+    g_assert_true(atspi_table_cell_get_position(ATSPI_TABLE_CELL(cell1.get()), &row, &column, nullptr));
+    g_assert_cmpint(row, ==, 0);
+    g_assert_cmpint(column, ==, 1);
+    atspi_table_cell_get_row_column_span(ATSPI_TABLE_CELL(cell1.get()), &row, &column, &rowSpan, &columnSpan, nullptr);
+    g_assert_cmpint(row, ==, 0);
+    g_assert_cmpint(column, ==, 1);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 1);
+    cellTable = adoptGRef(atspi_table_cell_get_table(ATSPI_TABLE_CELL(cell1.get()), nullptr));
+    g_assert_true(table.get() == cellTable.get());
+    rowHeaders = adoptGRef(atspi_table_cell_get_row_header_cells(ATSPI_TABLE_CELL(cell1.get()), nullptr));
+    g_assert_cmpint(rowHeaders->len, ==, 0);
+    columnHeaders = adoptGRef(atspi_table_cell_get_column_header_cells(ATSPI_TABLE_CELL(cell1.get()), nullptr));
+    g_assert_cmpint(columnHeaders->len, ==, 0);
+    g_assert_true(ATSPI_IS_TEXT(cell1.get()));
+    text.reset(atspi_text_get_text(ATSPI_TEXT(cell1.get()), 0, -1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Column 2");
+
+    g_assert_cmpint(atspi_table_get_index_at(ATSPI_TABLE(table.get()), 0, 2, nullptr), ==, 2);
+    g_assert_cmpint(atspi_table_get_row_at_index(ATSPI_TABLE(table.get()), 2, nullptr), ==, 0);
+    g_assert_cmpint(atspi_table_get_column_at_index(ATSPI_TABLE(table.get()), 2, nullptr), ==, 2);
+    g_assert_cmpint(atspi_table_get_row_extent_at(ATSPI_TABLE(table.get()), 0, 2, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_column_extent_at(ATSPI_TABLE(table.get()), 0, 2, nullptr), ==, 1);
+    g_assert_true(atspi_table_get_row_column_extents_at_index(ATSPI_TABLE(table.get()), 2, &row, &column, &rowSpan, &columnSpan, &isSelected, nullptr));
+    g_assert_cmpint(row, ==, 0);
+    g_assert_cmpint(column, ==, 2);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 1);
+    g_assert_false(isSelected);
+    auto cell2 = adoptGRef(atspi_table_get_accessible_at(ATSPI_TABLE(table.get()), 0, 2, nullptr));
+    g_assert_true(ATSPI_IS_TABLE_CELL(cell2.get()));
+    g_assert_true(cell2.get() == columnHeader2.get());
+    g_assert_cmpint(atspi_table_cell_get_row_span(ATSPI_TABLE_CELL(cell2.get()), nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_cell_get_column_span(ATSPI_TABLE_CELL(cell2.get()), nullptr), ==, 1);
+    g_assert_true(atspi_table_cell_get_position(ATSPI_TABLE_CELL(cell2.get()), &row, &column, nullptr));
+    g_assert_cmpint(row, ==, 0);
+    g_assert_cmpint(column, ==, 2);
+    atspi_table_cell_get_row_column_span(ATSPI_TABLE_CELL(cell2.get()), &row, &column, &rowSpan, &columnSpan, nullptr);
+    g_assert_cmpint(row, ==, 0);
+    g_assert_cmpint(column, ==, 2);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 1);
+    cellTable = adoptGRef(atspi_table_cell_get_table(ATSPI_TABLE_CELL(cell2.get()), nullptr));
+    g_assert_true(table.get() == cellTable.get());
+    rowHeaders = adoptGRef(atspi_table_cell_get_row_header_cells(ATSPI_TABLE_CELL(cell2.get()), nullptr));
+    g_assert_cmpint(rowHeaders->len, ==, 0);
+    columnHeaders = adoptGRef(atspi_table_cell_get_column_header_cells(ATSPI_TABLE_CELL(cell2.get()), nullptr));
+    g_assert_cmpint(columnHeaders->len, ==, 0);
+    g_assert_true(ATSPI_IS_TEXT(cell2.get()));
+    text.reset(atspi_text_get_text(ATSPI_TEXT(cell2.get()), 0, -1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Column 3");
+
+    g_assert_cmpint(atspi_table_get_index_at(ATSPI_TABLE(table.get()), 1, 0, nullptr), ==, 3);
+    g_assert_cmpint(atspi_table_get_row_at_index(ATSPI_TABLE(table.get()), 3, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_column_at_index(ATSPI_TABLE(table.get()), 3, nullptr), ==, 0);
+    g_assert_cmpint(atspi_table_get_row_extent_at(ATSPI_TABLE(table.get()), 1, 0, nullptr), ==, 2);
+    g_assert_cmpint(atspi_table_get_column_extent_at(ATSPI_TABLE(table.get()), 1, 0, nullptr), ==, 1);
+    g_assert_true(atspi_table_get_row_column_extents_at_index(ATSPI_TABLE(table.get()), 3, &row, &column, &rowSpan, &columnSpan, &isSelected, nullptr));
+    g_assert_cmpint(row, ==, 1);
+    g_assert_cmpint(column, ==, 0);
+    g_assert_cmpint(rowSpan, ==, 2);
+    g_assert_cmpint(columnSpan, ==, 1);
+    g_assert_false(isSelected);
+    auto cell3 = adoptGRef(atspi_table_get_accessible_at(ATSPI_TABLE(table.get()), 1, 0, nullptr));
+    g_assert_true(ATSPI_IS_TABLE_CELL(cell3.get()));
+    g_assert_true(cell3.get() == rowHeader.get());
+    g_assert_cmpint(atspi_table_cell_get_row_span(ATSPI_TABLE_CELL(cell3.get()), nullptr), ==, 2);
+    g_assert_cmpint(atspi_table_cell_get_column_span(ATSPI_TABLE_CELL(cell3.get()), nullptr), ==, 1);
+    g_assert_true(atspi_table_cell_get_position(ATSPI_TABLE_CELL(cell3.get()), &row, &column, nullptr));
+    g_assert_cmpint(row, ==, 1);
+    g_assert_cmpint(column, ==, 0);
+    atspi_table_cell_get_row_column_span(ATSPI_TABLE_CELL(cell3.get()), &row, &column, &rowSpan, &columnSpan, nullptr);
+    g_assert_cmpint(row, ==, 1);
+    g_assert_cmpint(column, ==, 0);
+    g_assert_cmpint(rowSpan, ==, 2);
+    g_assert_cmpint(columnSpan, ==, 1);
+    cellTable = adoptGRef(atspi_table_cell_get_table(ATSPI_TABLE_CELL(cell3.get()), nullptr));
+    g_assert_true(table.get() == cellTable.get());
+    rowHeaders = adoptGRef(atspi_table_cell_get_row_header_cells(ATSPI_TABLE_CELL(cell3.get()), nullptr));
+    g_assert_cmpint(rowHeaders->len, ==, 0);
+    columnHeaders = adoptGRef(atspi_table_cell_get_column_header_cells(ATSPI_TABLE_CELL(cell3.get()), nullptr));
+    g_assert_cmpint(columnHeaders->len, ==, 0);
+    g_assert_true(ATSPI_IS_TEXT(cell3.get()));
+    text.reset(atspi_text_get_text(ATSPI_TEXT(cell3.get()), 0, -1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Row 1 Cell 1");
+
+    g_assert_cmpint(atspi_table_get_index_at(ATSPI_TABLE(table.get()), 1, 1, nullptr), ==, 4);
+    g_assert_cmpint(atspi_table_get_row_at_index(ATSPI_TABLE(table.get()), 4, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_column_at_index(ATSPI_TABLE(table.get()), 4, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_row_extent_at(ATSPI_TABLE(table.get()), 1, 1, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_column_extent_at(ATSPI_TABLE(table.get()), 1, 1, nullptr), ==, 1);
+    g_assert_true(atspi_table_get_row_column_extents_at_index(ATSPI_TABLE(table.get()), 4, &row, &column, &rowSpan, &columnSpan, &isSelected, nullptr));
+    g_assert_cmpint(row, ==, 1);
+    g_assert_cmpint(column, ==, 1);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 1);
+    g_assert_false(isSelected);
+    auto cell4 = adoptGRef(atspi_table_get_accessible_at(ATSPI_TABLE(table.get()), 1, 1, nullptr));
+    g_assert_true(ATSPI_IS_TABLE_CELL(cell4.get()));
+    g_assert_cmpint(atspi_table_cell_get_row_span(ATSPI_TABLE_CELL(cell4.get()), nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_cell_get_column_span(ATSPI_TABLE_CELL(cell4.get()), nullptr), ==, 1);
+    g_assert_true(atspi_table_cell_get_position(ATSPI_TABLE_CELL(cell4.get()), &row, &column, nullptr));
+    g_assert_cmpint(row, ==, 1);
+    g_assert_cmpint(column, ==, 1);
+    atspi_table_cell_get_row_column_span(ATSPI_TABLE_CELL(cell4.get()), &row, &column, &rowSpan, &columnSpan, nullptr);
+    g_assert_cmpint(row, ==, 1);
+    g_assert_cmpint(column, ==, 1);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 1);
+    cellTable = adoptGRef(atspi_table_cell_get_table(ATSPI_TABLE_CELL(cell4.get()), nullptr));
+    g_assert_true(table.get() == cellTable.get());
+    rowHeaders = adoptGRef(atspi_table_cell_get_row_header_cells(ATSPI_TABLE_CELL(cell4.get()), nullptr));
+    g_assert_cmpint(rowHeaders->len, ==, 1);
+    g_assert_true(rowHeaders->pdata[0] == rowHeader.get());
+    g_ptr_array_foreach(rowHeaders.get(), reinterpret_cast<GFunc>(reinterpret_cast<GCallback>(g_object_unref)), nullptr);
+    columnHeaders = adoptGRef(atspi_table_cell_get_column_header_cells(ATSPI_TABLE_CELL(cell4.get()), nullptr));
+    g_assert_cmpint(columnHeaders->len, ==, 1);
+    g_assert_true(columnHeaders->pdata[0] == columnHeader1.get());
+    g_ptr_array_foreach(columnHeaders.get(), reinterpret_cast<GFunc>(reinterpret_cast<GCallback>(g_object_unref)), nullptr);
+    g_assert_true(ATSPI_IS_TEXT(cell4.get()));
+    text.reset(atspi_text_get_text(ATSPI_TEXT(cell4.get()), 0, -1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Row 1 Cell 2");
+
+    g_assert_cmpint(atspi_table_get_index_at(ATSPI_TABLE(table.get()), 1, 2, nullptr), ==, 5);
+    g_assert_cmpint(atspi_table_get_row_at_index(ATSPI_TABLE(table.get()), 5, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_column_at_index(ATSPI_TABLE(table.get()), 5, nullptr), ==, 2);
+    g_assert_cmpint(atspi_table_get_row_extent_at(ATSPI_TABLE(table.get()), 1, 2, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_column_extent_at(ATSPI_TABLE(table.get()), 1, 2, nullptr), ==, 1);
+    g_assert_true(atspi_table_get_row_column_extents_at_index(ATSPI_TABLE(table.get()), 5, &row, &column, &rowSpan, &columnSpan, &isSelected, nullptr));
+    g_assert_cmpint(row, ==, 1);
+    g_assert_cmpint(column, ==, 2);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 1);
+    g_assert_false(isSelected);
+    auto cell5 = adoptGRef(atspi_table_get_accessible_at(ATSPI_TABLE(table.get()), 1, 2, nullptr));
+    g_assert_true(ATSPI_IS_TABLE_CELL(cell5.get()));
+    g_assert_cmpint(atspi_table_cell_get_row_span(ATSPI_TABLE_CELL(cell5.get()), nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_cell_get_column_span(ATSPI_TABLE_CELL(cell5.get()), nullptr), ==, 1);
+    g_assert_true(atspi_table_cell_get_position(ATSPI_TABLE_CELL(cell5.get()), &row, &column, nullptr));
+    g_assert_cmpint(row, ==, 1);
+    g_assert_cmpint(column, ==, 2);
+    atspi_table_cell_get_row_column_span(ATSPI_TABLE_CELL(cell5.get()), &row, &column, &rowSpan, &columnSpan, nullptr);
+    g_assert_cmpint(row, ==, 1);
+    g_assert_cmpint(column, ==, 2);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 1);
+    cellTable = adoptGRef(atspi_table_cell_get_table(ATSPI_TABLE_CELL(cell5.get()), nullptr));
+    g_assert_true(table.get() == cellTable.get());
+    rowHeaders = adoptGRef(atspi_table_cell_get_row_header_cells(ATSPI_TABLE_CELL(cell5.get()), nullptr));
+    g_assert_cmpint(rowHeaders->len, ==, 1);
+    g_assert_true(rowHeaders->pdata[0] == rowHeader.get());
+    g_ptr_array_foreach(rowHeaders.get(), reinterpret_cast<GFunc>(reinterpret_cast<GCallback>(g_object_unref)), nullptr);
+    columnHeaders = adoptGRef(atspi_table_cell_get_column_header_cells(ATSPI_TABLE_CELL(cell5.get()), nullptr));
+    g_assert_cmpint(columnHeaders->len, ==, 1);
+    g_assert_true(columnHeaders->pdata[0] == columnHeader2.get());
+    g_ptr_array_foreach(columnHeaders.get(), reinterpret_cast<GFunc>(reinterpret_cast<GCallback>(g_object_unref)), nullptr);
+    g_assert_true(ATSPI_IS_TEXT(cell5.get()));
+    text.reset(atspi_text_get_text(ATSPI_TEXT(cell5.get()), 0, -1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Row 1 Cell 3");
+
+    g_assert_cmpint(atspi_table_get_index_at(ATSPI_TABLE(table.get()), 2, 0, nullptr), ==, 3);
+    g_assert_cmpint(atspi_table_get_row_extent_at(ATSPI_TABLE(table.get()), 2, 0, nullptr), ==, 2);
+    g_assert_cmpint(atspi_table_get_column_extent_at(ATSPI_TABLE(table.get()), 2, 0, nullptr), ==, 1);
+    auto cell = adoptGRef(atspi_table_get_accessible_at(ATSPI_TABLE(table.get()), 2, 0, nullptr));
+    g_assert_true(cell3.get() == cell.get());
+
+    g_assert_cmpint(atspi_table_get_index_at(ATSPI_TABLE(table.get()), 2, 1, nullptr), ==, 6);
+    g_assert_cmpint(atspi_table_get_row_at_index(ATSPI_TABLE(table.get()), 6, nullptr), ==, 2);
+    g_assert_cmpint(atspi_table_get_column_at_index(ATSPI_TABLE(table.get()), 6, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_row_extent_at(ATSPI_TABLE(table.get()), 2, 1, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_column_extent_at(ATSPI_TABLE(table.get()), 2, 1, nullptr), ==, 1);
+    g_assert_true(atspi_table_get_row_column_extents_at_index(ATSPI_TABLE(table.get()), 6, &row, &column, &rowSpan, &columnSpan, &isSelected, nullptr));
+    g_assert_cmpint(row, ==, 2);
+    g_assert_cmpint(column, ==, 1);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 1);
+    g_assert_false(isSelected);
+    auto cell6 = adoptGRef(atspi_table_get_accessible_at(ATSPI_TABLE(table.get()), 2, 1, nullptr));
+    g_assert_true(ATSPI_IS_TABLE_CELL(cell6.get()));
+    g_assert_cmpint(atspi_table_cell_get_row_span(ATSPI_TABLE_CELL(cell6.get()), nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_cell_get_column_span(ATSPI_TABLE_CELL(cell6.get()), nullptr), ==, 1);
+    g_assert_true(atspi_table_cell_get_position(ATSPI_TABLE_CELL(cell6.get()), &row, &column, nullptr));
+    g_assert_cmpint(row, ==, 2);
+    g_assert_cmpint(column, ==, 1);
+    atspi_table_cell_get_row_column_span(ATSPI_TABLE_CELL(cell6.get()), &row, &column, &rowSpan, &columnSpan, nullptr);
+    g_assert_cmpint(row, ==, 2);
+    g_assert_cmpint(column, ==, 1);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 1);
+    cellTable = adoptGRef(atspi_table_cell_get_table(ATSPI_TABLE_CELL(cell6.get()), nullptr));
+    g_assert_true(table.get() == cellTable.get());
+    rowHeaders = adoptGRef(atspi_table_cell_get_row_header_cells(ATSPI_TABLE_CELL(cell6.get()), nullptr));
+    g_assert_cmpint(rowHeaders->len, ==, 1);
+    g_assert_true(rowHeaders->pdata[0] == rowHeader.get());
+    g_ptr_array_foreach(rowHeaders.get(), reinterpret_cast<GFunc>(reinterpret_cast<GCallback>(g_object_unref)), nullptr);
+    columnHeaders = adoptGRef(atspi_table_cell_get_column_header_cells(ATSPI_TABLE_CELL(cell6.get()), nullptr));
+    g_assert_cmpint(columnHeaders->len, ==, 1);
+    g_assert_true(columnHeaders->pdata[0] == columnHeader1.get());
+    g_ptr_array_foreach(columnHeaders.get(), reinterpret_cast<GFunc>(reinterpret_cast<GCallback>(g_object_unref)), nullptr);
+    g_assert_true(ATSPI_IS_TEXT(cell6.get()));
+    text.reset(atspi_text_get_text(ATSPI_TEXT(cell6.get()), 0, -1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Row 2 Cell 2");
+
+    g_assert_cmpint(atspi_table_get_index_at(ATSPI_TABLE(table.get()), 2, 2, nullptr), ==, 7);
+    g_assert_cmpint(atspi_table_get_row_at_index(ATSPI_TABLE(table.get()), 7, nullptr), ==, 2);
+    g_assert_cmpint(atspi_table_get_column_at_index(ATSPI_TABLE(table.get()), 7, nullptr), ==, 2);
+    g_assert_cmpint(atspi_table_get_row_extent_at(ATSPI_TABLE(table.get()), 2, 2, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_column_extent_at(ATSPI_TABLE(table.get()), 2, 2, nullptr), ==, 1);
+    g_assert_true(atspi_table_get_row_column_extents_at_index(ATSPI_TABLE(table.get()), 7, &row, &column, &rowSpan, &columnSpan, &isSelected, nullptr));
+    g_assert_cmpint(row, ==, 2);
+    g_assert_cmpint(column, ==, 2);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 1);
+    g_assert_false(isSelected);
+    auto cell7 = adoptGRef(atspi_table_get_accessible_at(ATSPI_TABLE(table.get()), 2, 2, nullptr));
+    g_assert_true(ATSPI_IS_TABLE_CELL(cell7.get()));
+    g_assert_cmpint(atspi_table_cell_get_row_span(ATSPI_TABLE_CELL(cell7.get()), nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_cell_get_column_span(ATSPI_TABLE_CELL(cell7.get()), nullptr), ==, 1);
+    g_assert_true(atspi_table_cell_get_position(ATSPI_TABLE_CELL(cell7.get()), &row, &column, nullptr));
+    g_assert_cmpint(row, ==, 2);
+    g_assert_cmpint(column, ==, 2);
+    atspi_table_cell_get_row_column_span(ATSPI_TABLE_CELL(cell7.get()), &row, &column, &rowSpan, &columnSpan, nullptr);
+    g_assert_cmpint(row, ==, 2);
+    g_assert_cmpint(column, ==, 2);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 1);
+    cellTable = adoptGRef(atspi_table_cell_get_table(ATSPI_TABLE_CELL(cell7.get()), nullptr));
+    g_assert_true(table.get() == cellTable.get());
+    rowHeaders = adoptGRef(atspi_table_cell_get_row_header_cells(ATSPI_TABLE_CELL(cell7.get()), nullptr));
+    g_assert_cmpint(rowHeaders->len, ==, 1);
+    g_assert_true(rowHeaders->pdata[0] == rowHeader.get());
+    g_ptr_array_foreach(rowHeaders.get(), reinterpret_cast<GFunc>(reinterpret_cast<GCallback>(g_object_unref)), nullptr);
+    columnHeaders = adoptGRef(atspi_table_cell_get_column_header_cells(ATSPI_TABLE_CELL(cell7.get()), nullptr));
+    g_assert_cmpint(columnHeaders->len, ==, 1);
+    g_assert_true(columnHeaders->pdata[0] == columnHeader2.get());
+    g_ptr_array_foreach(columnHeaders.get(), reinterpret_cast<GFunc>(reinterpret_cast<GCallback>(g_object_unref)), nullptr);
+    g_assert_true(ATSPI_IS_TEXT(cell7.get()));
+    text.reset(atspi_text_get_text(ATSPI_TEXT(cell7.get()), 0, -1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Row 2 Cell 3");
+
+    g_assert_cmpint(atspi_table_get_index_at(ATSPI_TABLE(table.get()), 3, 0, nullptr), ==, 8);
+    g_assert_cmpint(atspi_table_get_row_at_index(ATSPI_TABLE(table.get()), 8, nullptr), ==, 3);
+    g_assert_cmpint(atspi_table_get_column_at_index(ATSPI_TABLE(table.get()), 8, nullptr), ==, 0);
+    g_assert_cmpint(atspi_table_get_row_extent_at(ATSPI_TABLE(table.get()), 3, 0, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_column_extent_at(ATSPI_TABLE(table.get()), 3, 0, nullptr), ==, 3);
+    g_assert_true(atspi_table_get_row_column_extents_at_index(ATSPI_TABLE(table.get()), 8, &row, &column, &rowSpan, &columnSpan, &isSelected, nullptr));
+    g_assert_cmpint(row, ==, 3);
+    g_assert_cmpint(column, ==, 0);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 3);
+    g_assert_false(isSelected);
+    auto cell8 = adoptGRef(atspi_table_get_accessible_at(ATSPI_TABLE(table.get()), 3, 0, nullptr));
+    g_assert_true(ATSPI_IS_TABLE_CELL(cell8.get()));
+    g_assert_cmpint(atspi_table_cell_get_row_span(ATSPI_TABLE_CELL(cell8.get()), nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_cell_get_column_span(ATSPI_TABLE_CELL(cell8.get()), nullptr), ==, 3);
+    g_assert_true(atspi_table_cell_get_position(ATSPI_TABLE_CELL(cell8.get()), &row, &column, nullptr));
+    g_assert_cmpint(row, ==, 3);
+    g_assert_cmpint(column, ==, 0);
+    atspi_table_cell_get_row_column_span(ATSPI_TABLE_CELL(cell8.get()), &row, &column, &rowSpan, &columnSpan, nullptr);
+    g_assert_cmpint(row, ==, 3);
+    g_assert_cmpint(column, ==, 0);
+    g_assert_cmpint(rowSpan, ==, 1);
+    g_assert_cmpint(columnSpan, ==, 3);
+    cellTable = adoptGRef(atspi_table_cell_get_table(ATSPI_TABLE_CELL(cell8.get()), nullptr));
+    g_assert_true(table.get() == cellTable.get());
+    rowHeaders = adoptGRef(atspi_table_cell_get_row_header_cells(ATSPI_TABLE_CELL(cell8.get()), nullptr));
+    g_assert_cmpint(rowHeaders->len, ==, 0);
+    g_ptr_array_foreach(rowHeaders.get(), reinterpret_cast<GFunc>(reinterpret_cast<GCallback>(g_object_unref)), nullptr);
+    columnHeaders = adoptGRef(atspi_table_cell_get_column_header_cells(ATSPI_TABLE_CELL(cell8.get()), nullptr));
+    g_assert_cmpint(columnHeaders->len, ==, 1);
+    g_assert_true(columnHeaders->pdata[0] == columnHeader0.get());
+    g_ptr_array_foreach(columnHeaders.get(), reinterpret_cast<GFunc>(reinterpret_cast<GCallback>(g_object_unref)), nullptr);
+    g_assert_true(ATSPI_IS_TEXT(cell8.get()));
+    text.reset(atspi_text_get_text(ATSPI_TEXT(cell8.get()), 0, -1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "Row 3 Cell 1");
+
+    g_assert_cmpint(atspi_table_get_index_at(ATSPI_TABLE(table.get()), 3, 1, nullptr), ==, 8);
+    g_assert_cmpint(atspi_table_get_row_extent_at(ATSPI_TABLE(table.get()), 3, 1, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_column_extent_at(ATSPI_TABLE(table.get()), 3, 1, nullptr), ==, 3);
+    cell = adoptGRef(atspi_table_get_accessible_at(ATSPI_TABLE(table.get()), 3, 1, nullptr));
+    g_assert_true(cell8.get() == cell.get());
+
+    g_assert_cmpint(atspi_table_get_index_at(ATSPI_TABLE(table.get()), 3, 2, nullptr), ==, 8);
+    g_assert_cmpint(atspi_table_get_row_extent_at(ATSPI_TABLE(table.get()), 3, 2, nullptr), ==, 1);
+    g_assert_cmpint(atspi_table_get_column_extent_at(ATSPI_TABLE(table.get()), 3, 2, nullptr), ==, 3);
+    cell = adoptGRef(atspi_table_get_accessible_at(ATSPI_TABLE(table.get()), 3, 2, nullptr));
+    g_assert_true(cell8.get() == cell.get());
+#endif
+}
+
 void beforeAll()
 {
     AccessibilityTest::add("WebKitAccessibility", "accessible/basic-hierarchy", testAccessibleBasicHierarchy);
@@ -2360,6 +2772,7 @@
     AccessibilityTest::add("WebKitAccessibility", "image/basic", testImageBasic);
     AccessibilityTest::add("WebKitAccessibility", "selection/listbox", testSelectionListBox);
     AccessibilityTest::add("WebKitAccessibility", "selection/menulist", testSelectionMenuList);
+    AccessibilityTest::add("WebKitAccessibility", "table/basic", testTableBasic);
 }
 
 void afterAll()

Modified: trunk/Tools/WebKitTestRunner/InjectedBundle/atspi/AccessibilityUIElementAtspi.cpp (286415 => 286416)


--- trunk/Tools/WebKitTestRunner/InjectedBundle/atspi/AccessibilityUIElementAtspi.cpp	2021-12-02 08:06:24 UTC (rev 286415)
+++ trunk/Tools/WebKitTestRunner/InjectedBundle/atspi/AccessibilityUIElementAtspi.cpp	2021-12-02 08:16:13 UTC (rev 286416)
@@ -156,7 +156,15 @@
 
 RefPtr<AccessibilityUIElement> AccessibilityUIElement::rowAtIndex(unsigned index)
 {
-    return nullptr;
+    if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
+        return nullptr;
+
+    m_element->updateBackingStore();
+    auto rows = m_element->rows();
+    if (index >= rows.size())
+        return nullptr;
+
+    return AccessibilityUIElement::create(rows[index].get());
 }
 
 RefPtr<AccessibilityUIElement> AccessibilityUIElement::selectedChildAtIndex(unsigned index) const
@@ -296,6 +304,21 @@
     return builder.toString();
 }
 
+static Vector<RefPtr<AccessibilityUIElement>> elementsVector(const Vector<RefPtr<WebCore::AccessibilityObjectAtspi>>& wrappers)
+{
+    Vector<RefPtr<AccessibilityUIElement>> elements;
+    elements.reserveInitialCapacity(wrappers.size());
+    for (auto& wrapper : wrappers)
+        elements.uncheckedAppend(AccessibilityUIElement::create(wrapper.get()));
+    return elements;
+}
+
+static String attributesOfElements(const Vector<RefPtr<WebCore::AccessibilityObjectAtspi>>& wrappers)
+{
+    auto elements = elementsVector(wrappers);
+    return attributesOfElements(elements);
+}
+
 JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfChildren()
 {
     m_element->updateBackingStore();
@@ -306,12 +329,7 @@
         children = m_element->children();
     });
 
-    Vector<RefPtr<AccessibilityUIElement>> elements;
-    elements.reserveInitialCapacity(children.size());
-    for (auto& child : children)
-        elements.uncheckedAppend(AccessibilityUIElement::create(child.get()));
-
-    return OpaqueJSString::tryCreate(attributesOfElements(elements)).leakRef();
+    return OpaqueJSString::tryCreate(attributesOfElements(children)).leakRef();
 }
 
 JSRetainPtr<JSStringRef> AccessibilityUIElement::allAttributes()
@@ -352,6 +370,18 @@
         return attributes.get("setsize").toDouble();
     if (attributeName == "AXARIAPosInSet")
         return attributes.get("posinset").toDouble();
+    if (attributeName == "AXARIAColumnCount")
+        return attributes.get("colcount").toDouble();
+    if (attributeName == "AXARIARowCount")
+        return attributes.get("rowcount").toDouble();
+    if (attributeName == "AXARIAColumnIndex")
+        return attributes.get("colindex").toDouble();
+    if (attributeName == "AXARIARowIndex")
+        return attributes.get("rowindex").toDouble();
+    if (attributeName == "AXARIAColumnSpan")
+        return attributes.get("colspan").toDouble();
+    if (attributeName == "AXARIARowSpan")
+        return attributes.get("rowspan").toDouble();
 
     return 0;
 }
@@ -361,14 +391,51 @@
     return nullptr;
 }
 
+static JSValueRef makeJSArray(const Vector<RefPtr<AccessibilityUIElement>>& elements)
+{
+    WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::singleton().page()->page());
+    JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
+
+    size_t elementCount = elements.size();
+    auto valueElements = makeUniqueArray<JSValueRef>(elementCount);
+    for (size_t i = 0; i < elementCount; i++)
+        valueElements[i] = JSObjectMake(context, elements[i]->wrapperClass(), elements[i].get());
+
+    return JSObjectMakeArray(context, elementCount, valueElements.get(), nullptr);
+}
+
 JSValueRef AccessibilityUIElement::rowHeaders() const
 {
-    return nullptr;
+    if (m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
+        return makeJSArray(elementsVector(m_element->rowHeaders()));
+
+    if (m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::TableCell)) {
+        Vector<RefPtr<WebCore::AccessibilityObjectAtspi>> headers;
+        s_controller->executeOnAXThreadAndWait([this, &headers] {
+            m_element->updateBackingStore();
+            headers = m_element->cellRowHeaders();
+        });
+        return makeJSArray(elementsVector(headers));
+    }
+
+    return makeJSArray({ });
 }
 
 JSValueRef AccessibilityUIElement::columnHeaders() const
 {
-    return nullptr;
+    if (m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
+        return makeJSArray(elementsVector(m_element->columnHeaders()));
+
+    if (m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::TableCell)) {
+        Vector<RefPtr<WebCore::AccessibilityObjectAtspi>> headers;
+        s_controller->executeOnAXThreadAndWait([this, &headers] {
+            m_element->updateBackingStore();
+            headers = m_element->cellColumnHeaders();
+        });
+        return makeJSArray(elementsVector(headers));
+    }
+
+    return makeJSArray({ });
 }
 
 RefPtr<AccessibilityUIElement> AccessibilityUIElement::uiElementAttributeValue(JSStringRef attribute) const
@@ -998,12 +1065,20 @@
 
 JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumnHeaders()
 {
-    return JSStringCreateWithCharacters(0, 0);
+    if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
+        return JSStringCreateWithCharacters(0, 0);
+
+    m_element->updateBackingStore();
+    return OpaqueJSString::tryCreate(attributesOfElements(m_element->columnHeaders())).leakRef();
 }
 
 JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRowHeaders()
 {
-    return JSStringCreateWithCharacters(0, 0);
+    if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
+        return JSStringCreateWithCharacters(0, 0);
+
+    m_element->updateBackingStore();
+    return OpaqueJSString::tryCreate(attributesOfElements(m_element->rowHeaders())).leakRef();
 }
 
 JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumns()
@@ -1013,12 +1088,20 @@
 
 JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRows()
 {
-    return JSStringCreateWithCharacters(0, 0);
+    if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
+        return JSStringCreateWithCharacters(0, 0);
+
+    m_element->updateBackingStore();
+    return OpaqueJSString::tryCreate(attributesOfElements(m_element->rows())).leakRef();
 }
 
 JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfVisibleCells()
 {
-    return JSStringCreateWithCharacters(0, 0);
+    if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
+        return JSStringCreateWithCharacters(0, 0);
+
+    m_element->updateBackingStore();
+    return OpaqueJSString::tryCreate(attributesOfElements(m_element->cells())).leakRef();
 }
 
 JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfHeader()
@@ -1028,12 +1111,28 @@
 
 int AccessibilityUIElement::rowCount()
 {
-    return 0;
+    if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
+        return 0;
+
+    int count;
+    s_controller->executeOnAXThreadAndWait([this, &count] {
+        m_element->updateBackingStore();
+        count = m_element->rowCount();
+    });
+    return count;
 }
 
 int AccessibilityUIElement::columnCount()
 {
-    return 0;
+    if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
+        return 0;
+
+    int count;
+    s_controller->executeOnAXThreadAndWait([this, &count] {
+        m_element->updateBackingStore();
+        count = m_element->columnCount();
+    });
+    return count;
 }
 
 int AccessibilityUIElement::indexInTable()
@@ -1043,17 +1142,54 @@
 
 JSRetainPtr<JSStringRef> AccessibilityUIElement::rowIndexRange()
 {
-    return JSStringCreateWithCharacters(0, 0);
+    if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::TableCell))
+        return JSStringCreateWithCharacters(0, 0);
+
+    std::optional<unsigned> position, span;
+    s_controller->executeOnAXThreadAndWait([this, &position, &span] {
+        m_element->updateBackingStore();
+        position = m_element->cellPosition().first;
+        span = m_element->rowSpan();
+    });
+
+    if (!position || !span)
+        return JSStringCreateWithCharacters(0, 0);
+
+    return OpaqueJSString::tryCreate(makeString('{', *position, ", ", *span, '}')).leakRef();
 }
 
 JSRetainPtr<JSStringRef> AccessibilityUIElement::columnIndexRange()
 {
-    return JSStringCreateWithCharacters(0, 0);
+    if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::TableCell))
+        return JSStringCreateWithCharacters(0, 0);
+
+    std::optional<unsigned> position, span;
+    s_controller->executeOnAXThreadAndWait([this, &position, &span] {
+        m_element->updateBackingStore();
+        position = m_element->cellPosition().second;
+        span = m_element->columnSpan();
+    });
+
+    if (!position || !span)
+        return JSStringCreateWithCharacters(0, 0);
+
+    return OpaqueJSString::tryCreate(makeString('{', *position, ", ", *span, '}')).leakRef();
 }
 
-RefPtr<AccessibilityUIElement> AccessibilityUIElement::cellForColumnAndRow(unsigned col, unsigned row)
+RefPtr<AccessibilityUIElement> AccessibilityUIElement::cellForColumnAndRow(unsigned column, unsigned row)
 {
-    return nullptr;
+    if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
+        return nullptr;
+
+    RefPtr<WebCore::AccessibilityObjectAtspi> cell;
+    s_controller->executeOnAXThreadAndWait([this, &cell, column, row] {
+        m_element->updateBackingStore();
+        cell = m_element->cell(row, column);
+    });
+    if (!cell)
+        return nullptr;
+
+    return AccessibilityUIElement::create(cell.get());
 }
 
 RefPtr<AccessibilityUIElement> AccessibilityUIElement::horizontalScrollbar() const
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to