Title: [287304] trunk
Revision
287304
Author
[email protected]
Date
2021-12-21 00:46:55 -0800 (Tue, 21 Dec 2021)

Log Message

[GTK][a11y] Implement list markers when building with ATSPI
https://bugs.webkit.org/show_bug.cgi?id=234485

Reviewed by Adrian Perez de Castro.

Source/WebCore:

For list item markers in ATSPI we are following the chromium approach, exposing the markers as hyperlinks, so
included in the list item text string as the object replacement character. This approach also allows to expose
image markers. Since we don't have a list marker role in ATSPI, we use either text or image roles. New methods
have been added to get the role, role name and localized role name for the cases in which there isn't a direct
match between the webcore role and the atspi role.

* accessibility/AccessibilityRenderObject.cpp:
(WebCore::AccessibilityRenderObject::stringValue const): In ATSPI we want the text with suffix.
* accessibility/atspi/AccessibilityObjectAtspi.cpp:
(WebCore::AccessibilityObjectAtspi::interfacesForObject): Make item markers implement hyperlink interface and
either text or image.
(WebCore::atspiRole): Assert if ListMarker role is passed since that's no longer possible.
(WebCore::AccessibilityObjectAtspi::effectiveRole const): Handle the ListMarker role as special case case.
(WebCore::AccessibilityObjectAtspi::role const): Check if there's an effective role first.
(WebCore::AccessibilityObjectAtspi::effectiveRoleName const): Handle effective role here.
(WebCore::AccessibilityObjectAtspi::roleName const): Check if there's an effective role first.
(WebCore::AccessibilityObjectAtspi::effectiveLocalizedRoleName const): Handle effective role here.
(WebCore::AccessibilityObjectAtspi::localizedRoleName const): Check if there's an effective role first.
* accessibility/atspi/AccessibilityObjectAtspi.h:
* accessibility/atspi/AccessibilityObjectTextAtspi.cpp:
(WebCore::AccessibilityObjectAtspi::text const): Remove the main thread micro-optimization to avoid more
duplicated code and insert the object replacement character for list items having a marker.
(WebCore::adjustInputOffset): Helper function to adjust the input offset in case of having a list item marker.
(WebCore::adjustOutputOffset): Helper function to adjust the output offset in case of having a list item marker.
(WebCore::AccessibilityObjectAtspi::textInserted): Use adjustOutputOffset().
(WebCore::AccessibilityObjectAtspi::textDeleted): Ditto.
(WebCore::AccessibilityObjectAtspi::boundaryOffset const): Adjust offsets and handle words as special case to
always consider the list marker as a word.
(WebCore::AccessibilityObjectAtspi::offsetAtPoint const): Use adjustOutputOffset().
(WebCore::AccessibilityObjectAtspi::textAttributes const): Adjust offsets and handle list item marker as special
case to always consider the marker as an independent text run.

Tools:

Add test cases to check list item markers.

* TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp:
(testAccessibleListMarkers):
(testTextListMarkers):
(testHyperlinkBasic):
(testHypertextBasic):
(beforeAll):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (287303 => 287304)


--- trunk/Source/WebCore/ChangeLog	2021-12-21 08:45:38 UTC (rev 287303)
+++ trunk/Source/WebCore/ChangeLog	2021-12-21 08:46:55 UTC (rev 287304)
@@ -1,5 +1,44 @@
 2021-12-21  Carlos Garcia Campos  <[email protected]>
 
+        [GTK][a11y] Implement list markers when building with ATSPI
+        https://bugs.webkit.org/show_bug.cgi?id=234485
+
+        Reviewed by Adrian Perez de Castro.
+
+        For list item markers in ATSPI we are following the chromium approach, exposing the markers as hyperlinks, so
+        included in the list item text string as the object replacement character. This approach also allows to expose
+        image markers. Since we don't have a list marker role in ATSPI, we use either text or image roles. New methods
+        have been added to get the role, role name and localized role name for the cases in which there isn't a direct
+        match between the webcore role and the atspi role.
+
+        * accessibility/AccessibilityRenderObject.cpp:
+        (WebCore::AccessibilityRenderObject::stringValue const): In ATSPI we want the text with suffix.
+        * accessibility/atspi/AccessibilityObjectAtspi.cpp:
+        (WebCore::AccessibilityObjectAtspi::interfacesForObject): Make item markers implement hyperlink interface and
+        either text or image.
+        (WebCore::atspiRole): Assert if ListMarker role is passed since that's no longer possible.
+        (WebCore::AccessibilityObjectAtspi::effectiveRole const): Handle the ListMarker role as special case case.
+        (WebCore::AccessibilityObjectAtspi::role const): Check if there's an effective role first.
+        (WebCore::AccessibilityObjectAtspi::effectiveRoleName const): Handle effective role here.
+        (WebCore::AccessibilityObjectAtspi::roleName const): Check if there's an effective role first.
+        (WebCore::AccessibilityObjectAtspi::effectiveLocalizedRoleName const): Handle effective role here.
+        (WebCore::AccessibilityObjectAtspi::localizedRoleName const): Check if there's an effective role first.
+        * accessibility/atspi/AccessibilityObjectAtspi.h:
+        * accessibility/atspi/AccessibilityObjectTextAtspi.cpp:
+        (WebCore::AccessibilityObjectAtspi::text const): Remove the main thread micro-optimization to avoid more
+        duplicated code and insert the object replacement character for list items having a marker.
+        (WebCore::adjustInputOffset): Helper function to adjust the input offset in case of having a list item marker.
+        (WebCore::adjustOutputOffset): Helper function to adjust the output offset in case of having a list item marker.
+        (WebCore::AccessibilityObjectAtspi::textInserted): Use adjustOutputOffset().
+        (WebCore::AccessibilityObjectAtspi::textDeleted): Ditto.
+        (WebCore::AccessibilityObjectAtspi::boundaryOffset const): Adjust offsets and handle words as special case to
+        always consider the list marker as a word.
+        (WebCore::AccessibilityObjectAtspi::offsetAtPoint const): Use adjustOutputOffset().
+        (WebCore::AccessibilityObjectAtspi::textAttributes const): Adjust offsets and handle list item marker as special
+        case to always consider the marker as an independent text run.
+
+2021-12-21  Carlos Garcia Campos  <[email protected]>
+
         CSP: Include the sample in eval violation reports
         https://bugs.webkit.org/show_bug.cgi?id=234390
 

Modified: trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp (287303 => 287304)


--- trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp	2021-12-21 08:45:38 UTC (rev 287303)
+++ trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp	2021-12-21 08:46:55 UTC (rev 287304)
@@ -781,10 +781,15 @@
         }
         return downcast<RenderMenuList>(*m_renderer).text();
     }
-    
-    if (is<RenderListMarker>(*m_renderer))
+
+    if (is<RenderListMarker>(*m_renderer)) {
+#if USE(ATSPI)
+        return downcast<RenderListMarker>(*m_renderer).textWithSuffix().toString();
+#else
         return downcast<RenderListMarker>(*m_renderer).textWithoutSuffix().toString();
-    
+#endif
+    }
+
     if (isWebArea())
         return String();
     

Modified: trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp (287303 => 287304)


--- trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp	2021-12-21 08:45:38 UTC (rev 287303)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp	2021-12-21 08:46:55 UTC (rev 287304)
@@ -73,16 +73,6 @@
             if ((renderer && renderer->childrenInline()) || roleIsTextType(coreObject.roleValue()) || coreObject.isMathToken())
                 interfaces.add(Interface::Text);
         }
-
-        // Add the Text interface for list items whose first accessible child has a text renderer.
-        if (coreObject.roleValue() == AccessibilityRole::ListItem) {
-            const auto& children = coreObject.children();
-            if (!children.isEmpty()) {
-                auto childInterfaces = interfacesForObject(*children[0]);
-                if (childInterfaces.contains(Interface::Text))
-                    interfaces.add(Interface::Text);
-            }
-        }
     }
 
     if (coreObject.supportsRangeValue())
@@ -109,6 +99,14 @@
         || coreObject.roleValue() == AccessibilityRole::RowHeader)
         interfaces.add(Interface::TableCell);
 
+    if (coreObject.roleValue() == AccessibilityRole::ListMarker && renderer) {
+        if (renderer->isImage())
+            interfaces.add(Interface::Image);
+        else
+            interfaces.add(Interface::Text);
+        interfaces.add(Interface::Hyperlink);
+    }
+
     return interfaces;
 }
 
@@ -272,8 +270,6 @@
     case AccessibilityRole::GraphicsSymbol:
     case AccessibilityRole::Image:
         return Atspi::Role::Image;
-    case AccessibilityRole::ListMarker:
-        return Atspi::Role::Text;
     case AccessibilityRole::DocumentArticle:
         return Atspi::Role::Article;
     case AccessibilityRole::Document:
@@ -393,6 +389,8 @@
     case AccessibilityRole::TableHeaderContainer:
     case AccessibilityRole::ValueIndicator:
         return Atspi::Role::Unknown;
+    case AccessibilityRole::ListMarker:
+        RELEASE_ASSERT_NOT_REACHED();
     }
 
     RELEASE_ASSERT_NOT_REACHED();
@@ -1243,6 +1241,30 @@
     m_root.atspi().loadEvent(*this, event);
 }
 
+std::optional<unsigned> AccessibilityObjectAtspi::effectiveRole() const
+{
+    RELEASE_ASSERT(!isMainThread());
+    RELEASE_ASSERT(m_axObject);
+
+    switch (m_axObject->roleValue()) {
+    case AccessibilityRole::ListMarker:
+        return Accessibility::retrieveValueFromMainThread<unsigned>([this]() -> unsigned {
+            if (m_coreObject)
+                m_coreObject->updateBackingStore();
+
+            if (!m_coreObject)
+                return Atspi::Role::InvalidRole;
+
+            auto* renderer = m_coreObject->renderer();
+            return renderer && renderer->isImage() ? Atspi::Role::Image : Atspi::Role::Text;
+        });
+    default:
+        break;
+    }
+
+    return std::nullopt;
+}
+
 unsigned AccessibilityObjectAtspi::role() const
 {
     RELEASE_ASSERT(!isMainThread());
@@ -1249,9 +1271,32 @@
     if (!m_axObject)
         return Atspi::Role::Unknown;
 
+    if (auto effective = effectiveRole())
+        return *effective;
+
     return atspiRole(m_axObject->roleValue());
 }
 
+String AccessibilityObjectAtspi::effectiveRoleName() const
+{
+    auto effective = effectiveRole();
+    if (!effective)
+        return { };
+
+    switch (*effective) {
+    case Atspi::Role::Image:
+        return "image";
+    case Atspi::Role::Text:
+        return "text";
+    case Atspi::Role::InvalidRole:
+        return "invalid";
+    default:
+        break;
+    }
+
+    RELEASE_ASSERT_NOT_REACHED();
+}
+
 String AccessibilityObjectAtspi::roleName() const
 {
     RELEASE_ASSERT(!isMainThread());
@@ -1258,9 +1303,33 @@
     if (!m_axObject)
         return "invalid";
 
+    auto effective = effectiveRoleName();
+    if (!effective.isEmpty())
+        return effective;
+
     return m_axObject->rolePlatformString();
 }
 
+const char* AccessibilityObjectAtspi::effectiveLocalizedRoleName() const
+{
+    auto effective = effectiveRole();
+    if (!effective)
+        return nullptr;
+
+    switch (*effective) {
+    case Atspi::Role::Image:
+        return AccessibilityAtspi::localizedRoleName(AccessibilityRole::Image);
+    case Atspi::Role::Text:
+        return AccessibilityAtspi::localizedRoleName(AccessibilityRole::StaticText);
+    case Atspi::Role::InvalidRole:
+        return _("invalid");
+    default:
+        break;
+    }
+
+    RELEASE_ASSERT_NOT_REACHED();
+}
+
 const char* AccessibilityObjectAtspi::localizedRoleName() const
 {
     RELEASE_ASSERT(!isMainThread());
@@ -1267,6 +1336,9 @@
     if (!m_axObject)
         return _("invalid");
 
+    if (const auto* effective = effectiveLocalizedRoleName())
+        return effective;
+
     return AccessibilityAtspi::localizedRoleName(m_axObject->roleValue());
 }
 

Modified: trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.h (287303 => 287304)


--- trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.h	2021-12-21 08:45:38 UTC (rev 287303)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.h	2021-12-21 08:46:55 UTC (rev 287304)
@@ -165,7 +165,10 @@
     void childAdded(AccessibilityObjectAtspi&);
     void childRemoved(AccessibilityObjectAtspi&);
 
+    std::optional<unsigned> effectiveRole() const;
+    String effectiveRoleName() const;
     String roleName() const;
+    const char* effectiveLocalizedRoleName() const;
     const char* localizedRoleName() const;
     void buildAttributes(GVariantBuilder*) const;
     void buildRelationSet(GVariantBuilder*) const;
@@ -243,6 +246,7 @@
     Atomic<bool> m_isRegistered { false };
     String m_path;
     String m_hyperlinkPath;
+    mutable std::atomic<bool> m_hasListMarkerAtStart;
     mutable int m_indexInParent { -1 };
     mutable Lock m_rootLock;
 };

Modified: trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTextAtspi.cpp (287303 => 287304)


--- trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTextAtspi.cpp	2021-12-21 08:45:38 UTC (rev 287303)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTextAtspi.cpp	2021-12-21 08:46:55 UTC (rev 287304)
@@ -29,10 +29,13 @@
 #include "Editing.h"
 #include "PlatformScreen.h"
 #include "RenderLayer.h"
+#include "RenderListItem.h"
+#include "RenderListMarker.h"
 #include "SurrogatePairAwareTextIterator.h"
 #include "TextIterator.h"
 #include "VisibleUnits.h"
 #include <gio/gio.h>
+#include <wtf/unicode/CharacterNames.h>
 
 namespace WebCore {
 
@@ -268,6 +271,8 @@
     if (!axObject)
         return { };
 
+    m_hasListMarkerAtStart.store(false);
+
 #if ENABLE(INPUT_TYPE_COLOR)
     if (axObject->roleValue() == AccessibilityRole::ColorWell) {
         auto color = convertColor<SRGBA<float>>(axObject->colorValue());
@@ -283,9 +288,6 @@
     if (!value.isNull())
         return value;
 
-    if (axObject == m_coreObject)
-        return m_coreObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren));
-
     return Accessibility::retrieveValueFromMainThread<String>([this]() -> String {
         if (m_coreObject)
             m_coreObject->updateBackingStore();
@@ -292,7 +294,18 @@
 
         if (!m_coreObject)
             return { };
-        return m_coreObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)).isolatedCopy();
+
+        auto text = m_coreObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren));
+        if (auto* renderer = m_coreObject->renderer()) {
+            if (is<RenderListItem>(*renderer) && downcast<RenderListItem>(*renderer).markerRenderer()) {
+                if (renderer->style().direction() == TextDirection::LTR) {
+                    text = makeString(objectReplacementCharacter, text);
+                    m_hasListMarkerAtStart.store(true);
+                } else
+                    text = makeString(text, objectReplacementCharacter);
+            }
+        }
+        return text;
     });
 }
 
@@ -345,6 +358,16 @@
     return substring.get();
 }
 
+static inline int adjustInputOffset(unsigned utf16Offset, bool hasListMarkerAtStart)
+{
+    return hasListMarkerAtStart && utf16Offset ? utf16Offset - 1 : utf16Offset;
+}
+
+static inline int adjustOutputOffset(unsigned utf16Offset, bool hasListMarkerAtStart)
+{
+    return hasListMarkerAtStart ? utf16Offset + 1 : utf16Offset;
+}
+
 void AccessibilityObjectAtspi::textInserted(const String& insertedText, const VisiblePosition& position)
 {
     RELEASE_ASSERT(isMainThread());
@@ -353,7 +376,7 @@
 
     auto utf16Text = text();
     auto utf8Text = utf16Text.utf8();
-    auto utf16Offset = m_coreObject->indexForVisiblePosition(position);
+    auto utf16Offset = adjustOutputOffset(m_coreObject->indexForVisiblePosition(position), m_hasListMarkerAtStart.load());
     auto mapping = offsetMapping(utf16Text);
     auto offset = UTF16OffsetToUTF8(mapping, utf16Offset);
     auto utf8InsertedText = insertedText.utf8();
@@ -369,7 +392,7 @@
 
     auto utf16Text = text();
     auto utf8Text = utf16Text.utf8();
-    auto utf16Offset = m_coreObject->indexForVisiblePosition(position);
+    auto utf16Offset = adjustOutputOffset(m_coreObject->indexForVisiblePosition(position), m_hasListMarkerAtStart.load());
     auto mapping = offsetMapping(utf16Text);
     auto offset = UTF16OffsetToUTF8(mapping, utf16Offset);
     auto utf8DeletedText = deletedText.utf8();
@@ -386,12 +409,15 @@
         if (!m_coreObject)
             return { };
 
-        VisiblePosition offsetPosition = m_coreObject->visiblePositionForIndex(utf16Offset);
+        VisiblePosition offsetPosition = m_coreObject->visiblePositionForIndex(adjustInputOffset(utf16Offset, m_hasListMarkerAtStart.load()));
         VisiblePosition startPosition, endPostion;
         switch (granularity) {
         case TextGranularity::Character:
             RELEASE_ASSERT_NOT_REACHED();
         case TextGranularity::WordStart: {
+            if (!utf16Offset && m_hasListMarkerAtStart.load())
+                return { 0, 1 };
+
             startPosition = isStartOfWord(offsetPosition) && deprecatedIsEditingWhitespace(offsetPosition.characterBefore()) ? offsetPosition : startOfWord(offsetPosition, LeftWordIfOnBoundary);
             endPostion = nextWordPosition(startPosition);
             auto positionAfterSpacingAndFollowingWord = nextWordPosition(endPostion);
@@ -405,6 +431,9 @@
             break;
         }
         case TextGranularity::WordEnd: {
+            if (!utf16Offset && m_hasListMarkerAtStart.load())
+                return { 0, 1 };
+
             startPosition = previousWordPosition(offsetPosition);
             auto positionBeforeSpacingAndPreviousWord = previousWordPosition(startPosition);
             if (positionBeforeSpacingAndPreviousWord != startPosition)
@@ -438,7 +467,12 @@
             endPostion = endOfParagraph(offsetPosition);
             break;
         }
-        return { m_coreObject->indexForVisiblePosition(startPosition), m_coreObject->indexForVisiblePosition(endPostion) };
+
+        auto startOffset = m_coreObject->indexForVisiblePosition(startPosition);
+        // For no word boundaries, include the list marker if start offset is 0.
+        if (!startOffset && m_hasListMarkerAtStart.load() && (granularity == TextGranularity::WordStart || granularity == TextGranularity::WordEnd))
+            startOffset = adjustOutputOffset(startOffset, m_hasListMarkerAtStart.load());
+        return { startOffset, adjustOutputOffset(m_coreObject->indexForVisiblePosition(endPostion), m_hasListMarkerAtStart.load()) };
     });
 }
 
@@ -618,7 +652,7 @@
         if (position.isNull())
             return -1;
 
-        return m_coreObject->indexForVisiblePosition(position);
+        return adjustOutputOffset(m_coreObject->indexForVisiblePosition(position), m_hasListMarkerAtStart.load());
     });
 
     if (utf16Offset == -1)
@@ -776,7 +810,7 @@
         if (m_coreObject)
             m_coreObject->updateBackingStore();
 
-        if (!m_coreObject || !m_coreObject->renderer() || !m_coreObject->node())
+        if (!m_coreObject || !m_coreObject->renderer())
             return { };
 
         auto accessibilityTextAttributes = [this](AXCoreObject* axObject, const HashMap<String, String>& defaultAttributes) -> HashMap<String, String> {
@@ -849,7 +883,24 @@
         if (!utf16Offset)
             return { defaultAttributes, -1, -1 };
 
-        VisiblePosition offsetPosition = m_coreObject->visiblePositionForIndex(*utf16Offset);
+        if (is<RenderListMarker>(*m_coreObject->renderer()))
+            return { defaultAttributes, 0, static_cast<int>(m_coreObject->stringValue().length()) };
+
+        if (!m_coreObject->node())
+            return { defaultAttributes, -1, -1 };
+
+        if (!*utf16Offset && m_hasListMarkerAtStart.load()) {
+            // Always consider list marker an independent run.
+            auto attributes = accessibilityTextAttributes(m_coreObject->children()[0].get(), defaultAttributes);
+            if (!includeDefault)
+                return { attributes, 0, 1 };
+
+            for (const auto& it : attributes)
+                defaultAttributes.set(it.key, it.value);
+            return { defaultAttributes, 0, 1 };
+        }
+
+        VisiblePosition offsetPosition = m_coreObject->visiblePositionForIndex(adjustInputOffset(*utf16Offset, m_hasListMarkerAtStart.load()));
         auto* childNode = offsetPosition.deepEquivalent().deprecatedNode();
         if (!childNode)
             return { defaultAttributes, -1, -1 };
@@ -894,13 +945,15 @@
             endPosition = lastPositionInOrAfterNode(endRenderer->node());
         }
 
+        auto startOffset = adjustOutputOffset(m_coreObject->indexForVisiblePosition(startPosition), m_hasListMarkerAtStart.load());
+        auto endOffset = adjustOutputOffset(m_coreObject->indexForVisiblePosition(endPosition), m_hasListMarkerAtStart.load());
         if (!includeDefault)
-            return { attributes, m_coreObject->indexForVisiblePosition(startPosition), m_coreObject->indexForVisiblePosition(endPosition) };
+            return { attributes, startOffset, endOffset };
 
         for (const auto& it : attributes)
             defaultAttributes.set(it.key, it.value);
 
-        return { defaultAttributes, m_coreObject->indexForVisiblePosition(startPosition), m_coreObject->indexForVisiblePosition(endPosition) };
+        return { defaultAttributes, startOffset, endOffset };
     });
 }
 

Modified: trunk/Tools/ChangeLog (287303 => 287304)


--- trunk/Tools/ChangeLog	2021-12-21 08:45:38 UTC (rev 287303)
+++ trunk/Tools/ChangeLog	2021-12-21 08:46:55 UTC (rev 287304)
@@ -1,3 +1,19 @@
+2021-12-21  Carlos Garcia Campos  <[email protected]>
+
+        [GTK][a11y] Implement list markers when building with ATSPI
+        https://bugs.webkit.org/show_bug.cgi?id=234485
+
+        Reviewed by Adrian Perez de Castro.
+
+        Add test cases to check list item markers.
+
+        * TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp:
+        (testAccessibleListMarkers):
+        (testTextListMarkers):
+        (testHyperlinkBasic):
+        (testHypertextBasic):
+        (beforeAll):
+
 2021-12-20  Alex Christensen  <[email protected]>
 
         REGRESSION: [ iOS ] 5 TestWebKitAPI.WebpagePreferences.* api tests are flaky timing out

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp (287303 => 287304)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp	2021-12-21 08:45:38 UTC (rev 287303)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp	2021-12-21 08:46:55 UTC (rev 287304)
@@ -872,6 +872,79 @@
     g_assert_cmpint(atspi_accessible_get_role(rootObject.get(), nullptr), ==, ATSPI_ROLE_FILLER);
 }
 
+static void testAccessibleListMarkers(AccessibilityTest* test, gconstpointer)
+{
+#if !USE(ATSPI)
+    g_test_skip("List markers are handled differently with ATK");
+#else
+    GUniquePtr<char> baseDir(g_strdup_printf("file://%s/", Test::getResourcesDir().data()));
+    test->showInWindow(800, 600);
+    test->loadHtml(
+        "<html>"
+        "  <head>"
+        "    <style>"
+        "      .img { list-style-image: url(blank.ico) }"
+        "    </style>"
+        "  </head>"
+        "  <body>"
+        "    <ol>"
+        "      <li>List item 1</li>"
+        "    </ol>"
+        "    <ul class='img'>"
+        "      <li>List item 1</li>"
+        "    </ul>"
+        "  </body>"
+        "</html>",
+        baseDir.get());
+    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), ==, 2);
+
+    auto ol = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(ol.get()));
+    g_assert_cmpint(atspi_accessible_get_role(ol.get(), nullptr), ==, ATSPI_ROLE_LIST);
+    g_assert_cmpint(atspi_accessible_get_child_count(ol.get(), nullptr), ==, 1);
+
+    auto li = adoptGRef(atspi_accessible_get_child_at_index(ol.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(li.get()));
+    g_assert_cmpint(atspi_accessible_get_role(li.get(), nullptr), ==, ATSPI_ROLE_LIST_ITEM);
+    g_assert_cmpint(atspi_accessible_get_child_count(li.get(), nullptr), ==, 1);
+    auto marker = adoptGRef(atspi_accessible_get_child_at_index(li.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(marker.get()));
+    g_assert_cmpint(atspi_accessible_get_role(marker.get(), nullptr), ==, ATSPI_ROLE_TEXT);
+    GUniquePtr<char> name(atspi_accessible_get_role_name(marker.get(), nullptr));
+    g_assert_cmpstr(name.get(), ==, "text");
+    name.reset(atspi_accessible_get_localized_role_name(marker.get(), nullptr));
+    g_assert_cmpstr(name.get(), ==, "text");
+    GRefPtr<AtspiText> text = adoptGRef(atspi_accessible_get_text_iface(marker.get()));
+    g_assert_nonnull(text.get());
+
+    auto ul = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 1, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(ul.get()));
+    g_assert_cmpint(atspi_accessible_get_role(ul.get(), nullptr), ==, ATSPI_ROLE_LIST);
+    g_assert_cmpint(atspi_accessible_get_child_count(ul.get(), nullptr), ==, 1);
+
+    li = adoptGRef(atspi_accessible_get_child_at_index(ul.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(li.get()));
+    g_assert_cmpint(atspi_accessible_get_role(li.get(), nullptr), ==, ATSPI_ROLE_LIST_ITEM);
+    g_assert_cmpint(atspi_accessible_get_child_count(li.get(), nullptr), ==, 1);
+    marker = adoptGRef(atspi_accessible_get_child_at_index(li.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(marker.get()));
+    g_assert_cmpint(atspi_accessible_get_role(marker.get(), nullptr), ==, ATSPI_ROLE_IMAGE);
+    name.reset(atspi_accessible_get_role_name(marker.get(), nullptr));
+    g_assert_cmpstr(name.get(), ==, "image");
+    name.reset(atspi_accessible_get_localized_role_name(marker.get(), nullptr));
+    g_assert_cmpstr(name.get(), ==, "image");
+    GRefPtr<AtspiImage> image = adoptGRef(atspi_accessible_get_image_iface(marker.get()));
+    g_assert_nonnull(image.get());
+#endif
+}
+
 static void testComponentHitTest(AccessibilityTest* test, gconstpointer)
 {
     test->showInWindow();
@@ -1762,6 +1835,120 @@
 #endif
 }
 
+static void testTextListMarkers(AccessibilityTest* test, gconstpointer)
+{
+#if !USE(ATSPI)
+    g_test_skip("List markers are handled differently with ATK");
+#else
+    test->showInWindow(800, 600);
+    test->loadHtml(
+        "<html>"
+        "  <body>"
+        "    <ul>"
+        "      <li>List <b>item</b> 1</li>"
+        "    </ul>"
+        "    <ol style='direction:rtl;'>"
+        "      <li>List <b>item</b> 1</li>"
+        "    </ol>"
+        "  </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), ==, 2);
+
+    auto ul = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(ul.get()));
+    g_assert_cmpint(atspi_accessible_get_child_count(ul.get(), nullptr), ==, 1);
+
+    auto li = adoptGRef(atspi_accessible_get_child_at_index(ul.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_TEXT(li.get()));
+    g_assert_cmpint(atspi_accessible_get_child_count(li.get(), nullptr), ==, 1);
+
+    auto length = atspi_text_get_character_count(ATSPI_TEXT(li.get()), nullptr);
+    g_assert_cmpint(length, ==, 12);
+
+    GUniquePtr<char> text(atspi_text_get_text(ATSPI_TEXT(li.get()), 0, -1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "\357\277\274List item 1");
+
+    UniqueAtspiTextRange range(atspi_text_get_string_at_offset(ATSPI_TEXT(li.get()), 0, ATSPI_TEXT_GRANULARITY_CHAR, nullptr));
+    g_assert_cmpstr(range->content, ==, "\357\277\274");
+    g_assert_cmpint(range->start_offset, ==, 0);
+    g_assert_cmpint(range->end_offset, ==, 1);
+    range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(li.get()), 1, ATSPI_TEXT_GRANULARITY_CHAR, nullptr));
+    g_assert_cmpstr(range->content, ==, "L");
+    g_assert_cmpint(range->start_offset, ==, 1);
+    g_assert_cmpint(range->end_offset, ==, 2);
+    range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(li.get()), 0, ATSPI_TEXT_GRANULARITY_WORD, nullptr));
+    g_assert_cmpstr(range->content, ==, "\357\277\274");
+    g_assert_cmpint(range->start_offset, ==, 0);
+    g_assert_cmpint(range->end_offset, ==, 1);
+    range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(li.get()), 1, ATSPI_TEXT_GRANULARITY_WORD, nullptr));
+    g_assert_cmpstr(range->content, ==, "List ");
+    g_assert_cmpint(range->start_offset, ==, 1);
+    g_assert_cmpint(range->end_offset, ==, 6);
+    range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(li.get()), 0, ATSPI_TEXT_GRANULARITY_LINE, nullptr));
+    g_assert_cmpstr(range->content, ==, "\357\277\274List item 1");
+    g_assert_cmpint(range->start_offset, ==, 0);
+    g_assert_cmpint(range->end_offset, ==, 12);
+    range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(li.get()), 1, ATSPI_TEXT_GRANULARITY_LINE, nullptr));
+    g_assert_cmpstr(range->content, ==, "\357\277\274List item 1");
+    g_assert_cmpint(range->start_offset, ==, 0);
+    g_assert_cmpint(range->end_offset, ==, 12);
+
+    int startOffset, endOffset;
+    GRefPtr<GHashTable> attributes = adoptGRef(atspi_text_get_attribute_run(ATSPI_TEXT(li.get()), 0, FALSE, &startOffset, &endOffset, nullptr));
+    g_assert_nonnull(attributes.get());
+    g_assert_cmpuint(g_hash_table_size(attributes.get()), ==, 0);
+    g_assert_cmpint(startOffset, ==, 0);
+    g_assert_cmpint(endOffset, ==, 1);
+    attributes = adoptGRef(atspi_text_get_attribute_run(ATSPI_TEXT(li.get()), 3, FALSE, &startOffset, &endOffset, nullptr));
+    g_assert_nonnull(attributes.get());
+    g_assert_cmpuint(g_hash_table_size(attributes.get()), ==, 0);
+    g_assert_cmpint(startOffset, ==, 1);
+    g_assert_cmpint(endOffset, ==, 6);
+    attributes = adoptGRef(atspi_text_get_attribute_run(ATSPI_TEXT(li.get()), 8, FALSE, &startOffset, &endOffset, nullptr));
+    g_assert_nonnull(attributes.get());
+    g_assert_cmpuint(g_hash_table_size(attributes.get()), >, 0);
+    g_assert_cmpint(startOffset, ==, 6);
+    g_assert_cmpint(endOffset, ==, 10);
+    attributes = adoptGRef(atspi_text_get_attribute_run(ATSPI_TEXT(li.get()), 11, FALSE, &startOffset, &endOffset, nullptr));
+    g_assert_nonnull(attributes.get());
+    g_assert_cmpuint(g_hash_table_size(attributes.get()), ==, 0);
+    g_assert_cmpint(startOffset, ==, 10);
+    g_assert_cmpint(endOffset, ==, 12);
+
+    auto marker = adoptGRef(atspi_accessible_get_child_at_index(li.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_TEXT(marker.get()));
+    text.reset(atspi_text_get_text(ATSPI_TEXT(marker.get()), 0, -1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "• ");
+
+    auto ol = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 1, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(ol.get()));
+    g_assert_cmpint(atspi_accessible_get_child_count(ol.get(), nullptr), ==, 1);
+
+    li = adoptGRef(atspi_accessible_get_child_at_index(ol.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_TEXT(li.get()));
+    g_assert_cmpint(atspi_accessible_get_child_count(li.get(), nullptr), ==, 1);
+
+    length = atspi_text_get_character_count(ATSPI_TEXT(li.get()), nullptr);
+    g_assert_cmpint(length, ==, 12);
+
+    text.reset(atspi_text_get_text(ATSPI_TEXT(li.get()), 0, -1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "List item 1\357\277\274");
+
+    marker = adoptGRef(atspi_accessible_get_child_at_index(li.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_TEXT(marker.get()));
+    text.reset(atspi_text_get_text(ATSPI_TEXT(marker.get()), 0, -1, nullptr));
+    g_assert_cmpstr(text.get(), ==, "1. ");
+#endif
+}
+
 static void testValueBasic(AccessibilityTest* test, gconstpointer)
 {
     test->showInWindow(800, 600);
@@ -1823,6 +2010,7 @@
         "    <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>"
+        "    <ul><li>List item</li></ul>"
         "  </body>"
         "</html>",
         nullptr);
@@ -1833,7 +2021,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), ==, 4);
+    g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 5);
 
     auto section = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
     g_assert_true(ATSPI_IS_ACCESSIBLE(section.get()));
@@ -1944,6 +2132,26 @@
     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());
+
+#if USE(ATSPI)
+    auto ul = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 4, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(ul.get()));
+    g_assert_cmpint(atspi_accessible_get_child_count(ul.get(), nullptr), ==, 1);
+    auto li = adoptGRef(atspi_accessible_get_child_at_index(ul.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(li.get()));
+    g_assert_cmpint(atspi_accessible_get_child_count(li.get(), nullptr), ==, 1);
+    auto marker = adoptGRef(atspi_accessible_get_child_at_index(li.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(marker.get()));
+    link = adoptGRef(atspi_accessible_get_hyperlink(marker.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);
+    g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 1);
+    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) == marker.get());
+#endif
 }
 
 static void testHypertextBasic(AccessibilityTest* test, gconstpointer)
@@ -1953,6 +2161,7 @@
         "<html>"
         "  <body>"
         "    <p>This is <button>button</button> and <a href=''>link</a> in a paragraph</p>"
+        "    <ol><li>List with a <a href=''>link</a></li></ol>"
         "  </body>"
         "</html>",
         nullptr);
@@ -1963,7 +2172,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_HYPERTEXT(p.get()));
@@ -2011,6 +2220,40 @@
     g_assert_cmpint(atspi_hypertext_get_link_index(ATSPI_HYPERTEXT(p.get()), 20, nullptr), ==, 1);
     g_assert_cmpint(atspi_hypertext_get_link_index(ATSPI_HYPERTEXT(p.get()), 24, nullptr), ==, -1);
 #endif
+
+#if USE(ATSPI)
+    auto ol = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 1, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(ol.get()));
+    g_assert_cmpint(atspi_accessible_get_child_count(ol.get(), nullptr), ==, 1);
+    auto li = adoptGRef(atspi_accessible_get_child_at_index(ol.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_HYPERTEXT(li.get()));
+    g_assert_cmpint(atspi_hypertext_get_n_links(ATSPI_HYPERTEXT(li.get()), nullptr), ==, 2);
+    link = adoptGRef(atspi_hypertext_get_link(ATSPI_HYPERTEXT(li.get()), 0, nullptr));
+    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);
+    g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 1);
+    uri.reset(atspi_hyperlink_get_uri(ATSPI_HYPERLINK(link.get()), 0, nullptr));
+    g_assert_cmpstr(uri.get(), ==, "");
+    auto marker = adoptGRef(atspi_accessible_get_child_at_index(li.get(), 0, nullptr));
+    g_assert_true(atspi_hyperlink_get_object(ATSPI_HYPERLINK(link.get()), 0, nullptr) == marker.get());
+    link = adoptGRef(atspi_hypertext_get_link(ATSPI_HYPERTEXT(li.get()), 1, nullptr));
+    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), ==, 13);
+    g_assert_cmpint(atspi_hyperlink_get_end_index(ATSPI_HYPERLINK(link.get()), nullptr), ==, 14);
+    uri.reset(atspi_hyperlink_get_uri(ATSPI_HYPERLINK(link.get()), 0, nullptr));
+    g_assert_cmpstr(uri.get(), ==, "https://www.webkit.org/");
+    a = adoptGRef(atspi_accessible_get_child_at_index(li.get(), 1, nullptr));
+    g_assert_true(atspi_hyperlink_get_object(ATSPI_HYPERLINK(link.get()), 0, nullptr) == a.get());
+
+    g_assert_cmpint(atspi_hypertext_get_link_index(ATSPI_HYPERTEXT(li.get()), 0, nullptr), ==, 0);
+    g_assert_cmpint(atspi_hypertext_get_link_index(ATSPI_HYPERTEXT(li.get()), 1, nullptr), ==, -1);
+    g_assert_cmpint(atspi_hypertext_get_link_index(ATSPI_HYPERTEXT(li.get()), 13, nullptr), ==, 1);
+    g_assert_cmpint(atspi_hypertext_get_link_index(ATSPI_HYPERTEXT(li.get()), 14, nullptr), ==, -1);
+#endif
 }
 
 static void testActionBasic(AccessibilityTest* test, gconstpointer)
@@ -2864,6 +3107,7 @@
     AccessibilityTest::add("WebKitAccessibility", "accessible/state", testAccessibleState);
     AccessibilityTest::add("WebKitAccessibility", "accessible/state-changed", testAccessibleStateChanged);
     AccessibilityTest::add("WebKitAccessibility", "accessible/event-listener", testAccessibleEventListener);
+    AccessibilityTest::add("WebKitAccessibility", "accessible/list-markers", testAccessibleListMarkers);
     AccessibilityTest::add("WebKitAccessibility", "component/hit-test", testComponentHitTest);
 #ifdef ATSPI_SCROLLTYPE_COUNT
     AccessibilityTest::add("WebKitAccessibility", "component/scroll-to", testComponentScrollTo);
@@ -2876,6 +3120,7 @@
     AccessibilityTest::add("WebKitAccessibility", "text/attributes", testTextAttributes);
     AccessibilityTest::add("WebKitAccessibility", "text/state-changed", testTextStateChanged);
     AccessibilityTest::add("WebKitAccessibility", "text/replaced-objects", testTextReplacedObjects);
+    AccessibilityTest::add("WebKitAccessibility", "text/list-markers", testTextListMarkers);
     AccessibilityTest::add("WebKitAccessibility", "value/basic", testValueBasic);
     AccessibilityTest::add("WebKitAccessibility", "hyperlink/basic", testHyperlinkBasic);
     AccessibilityTest::add("WebKitAccessibility", "hypertext/basic", testHypertextBasic);
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to