Title: [288808] trunk
Revision
288808
Author
[email protected]
Date
2022-01-31 02:56:44 -0800 (Mon, 31 Jan 2022)

Log Message

[GTK][a11y] Add support for password fields to ATSPI
https://bugs.webkit.org/show_bug.cgi?id=235778

Reviewed by Adrian Perez de Castro.

Source/WebCore:

* accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::postTextStateChangeNotification): Do not return early for password entries.
* accessibility/atspi/AccessibilityObjectAtspi.cpp:
(WebCore::AccessibilityObjectAtspi::effectiveRole const): Return PasswordText role for password fields.
(WebCore::AccessibilityObjectAtspi::effectiveRoleName const): Ditto.
(WebCore::AccessibilityObjectAtspi::effectiveLocalizedRoleName const): Ditto.
(WebCore::AccessibilityObject::accessibilityPlatformIncludesObject const): Ignore text control inner text elements.
* accessibility/atspi/AccessibilityObjectTextAtspi.cpp:
(WebCore::AccessibilityObjectAtspi::textInserted): Expose the rendered text in case of password field.
* html/HTMLTextFormControlElement.cpp:
(WebCore::HTMLTextFormControlElement::setInnerTextValue): Ditto.

Tools:

Add test cases for password inputs.

* TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp:
(testTextIterator):
(testTextSelections):
(testTextStateChanged):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (288807 => 288808)


--- trunk/Source/WebCore/ChangeLog	2022-01-31 08:48:47 UTC (rev 288807)
+++ trunk/Source/WebCore/ChangeLog	2022-01-31 10:56:44 UTC (rev 288808)
@@ -1,3 +1,22 @@
+2022-01-31  Carlos Garcia Campos  <[email protected]>
+
+        [GTK][a11y] Add support for password fields to ATSPI
+        https://bugs.webkit.org/show_bug.cgi?id=235778
+
+        Reviewed by Adrian Perez de Castro.
+
+        * accessibility/AXObjectCache.cpp:
+        (WebCore::AXObjectCache::postTextStateChangeNotification): Do not return early for password entries.
+        * accessibility/atspi/AccessibilityObjectAtspi.cpp:
+        (WebCore::AccessibilityObjectAtspi::effectiveRole const): Return PasswordText role for password fields.
+        (WebCore::AccessibilityObjectAtspi::effectiveRoleName const): Ditto.
+        (WebCore::AccessibilityObjectAtspi::effectiveLocalizedRoleName const): Ditto.
+        (WebCore::AccessibilityObject::accessibilityPlatformIncludesObject const): Ignore text control inner text elements.
+        * accessibility/atspi/AccessibilityObjectTextAtspi.cpp:
+        (WebCore::AccessibilityObjectAtspi::textInserted): Expose the rendered text in case of password field.
+        * html/HTMLTextFormControlElement.cpp:
+        (WebCore::HTMLTextFormControlElement::setInnerTextValue): Ditto.
+
 2022-01-30  Yusuke Suzuki  <[email protected]>
 
         [WTF] Add GenericHashKey

Modified: trunk/Source/WebCore/accessibility/AXObjectCache.cpp (288807 => 288808)


--- trunk/Source/WebCore/accessibility/AXObjectCache.cpp	2022-01-31 08:48:47 UTC (rev 288807)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.cpp	2022-01-31 10:56:44 UTC (rev 288808)
@@ -1446,7 +1446,7 @@
     m_isSynchronizingSelection = isSynchronizing;
 }
 
-#if PLATFORM(COCOA) || USE(ATSPI)
+#if PLATFORM(COCOA)
 static bool isPasswordFieldOrContainedByPasswordField(AccessibilityObject* object)
 {
     return object && (object->isPasswordField() || object->isContainedByPasswordField());
@@ -1510,9 +1510,10 @@
 
 #if PLATFORM(COCOA) || USE(ATSPI)
     if (object) {
+#if PLATFORM(COCOA)
         if (isPasswordFieldOrContainedByPasswordField(object))
             return;
-
+#endif
         if (auto observableObject = object->observableObject())
             object = observableObject;
     }

Modified: trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp (288807 => 288808)


--- trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp	2022-01-31 08:48:47 UTC (rev 288807)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp	2022-01-31 10:56:44 UTC (rev 288808)
@@ -30,6 +30,7 @@
 #include "RenderAncestorIterator.h"
 #include "RenderBlock.h"
 #include "RenderObject.h"
+#include "TextControlInnerElements.h"
 #include "TextIterator.h"
 #include <glib/gi18n-lib.h>
 #include <wtf/UUID.h>
@@ -1215,6 +1216,9 @@
 
 std::optional<unsigned> AccessibilityObjectAtspi::effectiveRole() const
 {
+    if (m_coreObject->isPasswordField())
+        return Atspi::Role::PasswordText;
+
     switch (m_coreObject->roleValue()) {
     case AccessibilityRole::ListMarker: {
         auto* renderer = m_coreObject->renderer();
@@ -1285,6 +1289,8 @@
         return "invalid";
     case Atspi::Role::Panel:
         return "panel";
+    case Atspi::Role::PasswordText:
+        return "password text";
     case Atspi::Role::Table:
         return "table";
     case Atspi::Role::TableRow:
@@ -1336,6 +1342,8 @@
         return _("invalid");
     case Atspi::Role::Panel:
         return AccessibilityAtspi::localizedRoleName(AccessibilityRole::Group);
+    case Atspi::Role::PasswordText:
+        return _("password text");
     case Atspi::Role::Table:
         return AccessibilityAtspi::localizedRoleName(AccessibilityRole::Table);
     case Atspi::Role::TableRow:
@@ -1481,6 +1489,9 @@
     if (is<HTMLSpanElement>(node) && !canSetFocusAttribute() && !hasAttributesRequiredForInclusion() && !supportsARIAAttributes())
         return AccessibilityObjectInclusion::IgnoreObject;
 
+    if (is<TextControlInnerTextElement>(node))
+        return AccessibilityObjectInclusion::IgnoreObject;
+
     return AccessibilityObjectInclusion::DefaultBehavior;
 }
 

Modified: trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTextAtspi.cpp (288807 => 288808)


--- trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTextAtspi.cpp	2022-01-31 08:48:47 UTC (rev 288807)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTextAtspi.cpp	2022-01-31 10:56:44 UTC (rev 288808)
@@ -363,9 +363,10 @@
     auto utf16Text = text();
     auto utf8Text = utf16Text.utf8();
     auto utf16Offset = adjustOutputOffset(m_coreObject->indexForVisiblePosition(position), m_hasListMarkerAtStart);
+    String maskedText = m_coreObject->isPasswordField() ? utf16Text.substring(utf16Offset - insertedText.length(), insertedText.length()) : String();
     auto mapping = offsetMapping(utf16Text);
     auto offset = UTF16OffsetToUTF8(mapping, utf16Offset);
-    auto utf8InsertedText = insertedText.utf8();
+    auto utf8InsertedText = maskedText.isNull() ? insertedText.utf8() : maskedText.utf8();
     auto insertedTextLength = g_utf8_strlen(utf8InsertedText.data(), -1);
     AccessibilityAtspi::singleton().textChanged(*this, "insert", WTFMove(utf8InsertedText), offset - insertedTextLength, insertedTextLength);
 }

Modified: trunk/Source/WebCore/html/HTMLTextFormControlElement.cpp (288807 => 288808)


--- trunk/Source/WebCore/html/HTMLTextFormControlElement.cpp	2022-01-31 08:48:47 UTC (rev 288807)
+++ trunk/Source/WebCore/html/HTMLTextFormControlElement.cpp	2022-01-31 10:56:44 UTC (rev 288808)
@@ -568,6 +568,16 @@
     if (textIsChanged || !innerText->hasChildNodes()) {
 #if ENABLE(ACCESSIBILITY) && !PLATFORM(COCOA)
         if (textIsChanged && renderer()) {
+#if USE(ATSPI)
+            if (is<HTMLInputElement>(*this) && downcast<HTMLInputElement>(*this).isPasswordField()) {
+                // Get the rendered text instead to not expose actual value to accessibility.
+                RenderObject* renderer = this->renderer();
+                while (renderer && !is<RenderText>(renderer))
+                    renderer = downcast<RenderElement>(*renderer).firstChild();
+                if (is<RenderText>(renderer))
+                    previousValue = downcast<RenderText>(renderer)->textWithoutConvertingBackslashToYenSymbol();
+            }
+#endif
             if (AXObjectCache* cache = document().existingAXObjectCache())
                 cache->postNotification(this, AXObjectCache::AXValueChanged, PostTarget::ObservableParent);
         }

Modified: trunk/Tools/ChangeLog (288807 => 288808)


--- trunk/Tools/ChangeLog	2022-01-31 08:48:47 UTC (rev 288807)
+++ trunk/Tools/ChangeLog	2022-01-31 10:56:44 UTC (rev 288808)
@@ -1,3 +1,17 @@
+2022-01-31  Carlos Garcia Campos  <[email protected]>
+
+        [GTK][a11y] Add support for password fields to ATSPI
+        https://bugs.webkit.org/show_bug.cgi?id=235778
+
+        Reviewed by Adrian Perez de Castro.
+
+        Add test cases for password inputs.
+
+        * TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp:
+        (testTextIterator):
+        (testTextSelections):
+        (testTextStateChanged):
+
 2022-01-30  Philippe Normand  <[email protected]>
 
         [GStreamer] GstStructure to JSON serialization

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp (288807 => 288808)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp	2022-01-31 08:48:47 UTC (rev 288807)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp	2022-01-31 10:56:44 UTC (rev 288808)
@@ -1302,6 +1302,44 @@
     g_assert_cmpstr(range->content, ==, "field");
     g_assert_cmpint(range->start_offset, ==, 14);
     g_assert_cmpint(range->end_offset, ==, 19);
+
+    // Password field now.
+    test->loadHtml(
+        "<html>"
+        "  <body>"
+        "    <input type='password' value='password value'/>"
+        "  </body>"
+        "</html>",
+        nullptr);
+    test->waitUntilLoadFinished();
+
+    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);
+
+    section = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(section.get()));
+    g_assert_cmpint(atspi_accessible_get_child_count(section.get(), nullptr), ==, 1);
+    input = adoptGRef(atspi_accessible_get_child_at_index(section.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_TEXT(input.get()));
+    length = atspi_text_get_character_count(ATSPI_TEXT(input.get()), nullptr);
+    g_assert_cmpint(length, ==, 14);
+    text.reset(atspi_text_get_text(ATSPI_TEXT(input.get()), 0, length, nullptr));
+    g_assert_cmpstr(text.get(), ==, "••••••••••••••");
+
+    range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(input.get()), 0, ATSPI_TEXT_GRANULARITY_CHAR, nullptr));
+    g_assert_cmpstr(range->content, ==, "•");
+    g_assert_cmpint(range->start_offset, ==, 0);
+    g_assert_cmpint(range->end_offset, ==, 1);
+    range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(input.get()), 13, ATSPI_TEXT_GRANULARITY_CHAR, nullptr));
+    g_assert_cmpstr(range->content, ==, "•");
+    g_assert_cmpint(range->start_offset, ==, 13);
+    g_assert_cmpint(range->end_offset, ==, 14);
+
+    range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(input.get()), 0, ATSPI_TEXT_GRANULARITY_WORD, nullptr));
+    g_assert_cmpstr(range->content, ==, "••••••••••••••");
+    g_assert_cmpint(range->start_offset, ==, 0);
+    g_assert_cmpint(range->end_offset, ==, 14);
 }
 
 static void testTextExtents(AccessibilityTest* test, gconstpointer)
@@ -1363,6 +1401,7 @@
         "  <body>"
         "    <p>This is a line of text</p>"
         "    <input value='This is text input value'/>"
+        "    <input type='password' value='secret'/>"
         "  </body>"
         "</html>",
         nullptr);
@@ -1415,7 +1454,7 @@
 
     auto section = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 1, nullptr));
     g_assert_true(ATSPI_IS_ACCESSIBLE(section.get()));
-    g_assert_cmpint(atspi_accessible_get_child_count(section.get(), nullptr), ==, 1);
+    g_assert_cmpint(atspi_accessible_get_child_count(section.get(), nullptr), ==, 2);
     auto input = adoptGRef(atspi_accessible_get_child_at_index(section.get(), 0, nullptr));
     g_assert_true(ATSPI_IS_TEXT(input.get()));
     g_assert_true(atspi_text_set_caret_offset(ATSPI_TEXT(input.get()), 5, nullptr));
@@ -1432,6 +1471,23 @@
     g_assert_cmpint(selection->end_offset, ==, 12);
     text.reset(atspi_text_get_text(ATSPI_TEXT(input.get()), selection->start_offset, selection->end_offset, nullptr));
     g_assert_cmpstr(text.get(), ==, "is text");
+
+    auto password = adoptGRef(atspi_accessible_get_child_at_index(section.get(), 1, nullptr));
+    g_assert_true(ATSPI_IS_TEXT(password.get()));
+    g_assert_true(atspi_text_set_caret_offset(ATSPI_TEXT(password.get()), 2, nullptr));
+    caretOffset = atspi_text_get_caret_offset(ATSPI_TEXT(password.get()), nullptr);
+    g_assert_cmpint(caretOffset, ==, 2);
+    g_assert_true(atspi_text_set_selection(ATSPI_TEXT(password.get()), 0, 2, 5, nullptr));
+    selectionCount = atspi_text_get_n_selections(ATSPI_TEXT(password.get()), nullptr);
+    g_assert_cmpuint(selectionCount, ==, 1);
+    caretOffset = atspi_text_get_caret_offset(ATSPI_TEXT(password.get()), nullptr);
+    g_assert_cmpint(caretOffset, ==, 5);
+    selection.reset(atspi_text_get_selection(ATSPI_TEXT(password.get()), 0, nullptr));
+    g_assert_nonnull(selection.get());
+    g_assert_cmpint(selection->start_offset, ==, 2);
+    g_assert_cmpint(selection->end_offset, ==, 5);
+    text.reset(atspi_text_get_text(ATSPI_TEXT(password.get()), selection->start_offset, selection->end_offset, nullptr));
+    g_assert_cmpstr(text.get(), ==, "•••");
 }
 
 static void testTextAttributes(AccessibilityTest* test, gconstpointer)
@@ -1558,6 +1614,7 @@
         "    <p>This is a line of text</p>"
         "    <input value='This is a text field'/>"
         "    <div contenteditable=true>This is content editable</div>"
+        "    <input type='password' value='123'/>"
         "  </body>"
         "</html>",
         nullptr);
@@ -1568,7 +1625,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), ==, 3);
+    g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 4);
 
     // Text caret moved.
     auto p = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
@@ -1601,6 +1658,18 @@
     g_assert_cmpstr(events[0]->type, ==, "object:text-caret-moved");
     g_assert_cmpuint(events[0]->detail1, ==, 15);
 
+    section = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 3, nullptr));
+    g_assert_true(ATSPI_IS_ACCESSIBLE(section.get()));
+    g_assert_cmpint(atspi_accessible_get_child_count(section.get(), nullptr), ==, 1);
+    auto password = adoptGRef(atspi_accessible_get_child_at_index(section.get(), 0, nullptr));
+    g_assert_true(ATSPI_IS_TEXT(password.get()));
+    test->startEventMonitor(password.get(), { "object:text-caret-moved" });
+    g_assert_true(atspi_text_set_caret_offset(ATSPI_TEXT(password.get()), 2, nullptr));
+    events = test->stopEventMonitor(1);
+    g_assert_cmpuint(events.size(), ==, 1);
+    g_assert_cmpstr(events[0]->type, ==, "object:text-caret-moved");
+    g_assert_cmpuint(events[0]->detail1, ==, 2);
+
 #if USE(ATSPI) // Selection changed seems to be broken with ATK.
     // Selection changed.
     test->startEventMonitor(p.get(), { "object:text-selection-changed", "object:text-caret-moved" });
@@ -1629,6 +1698,15 @@
     g_assert_nonnull(event);
     g_assert_cmpuint(event->detail1, ==, 18);
     g_assert_nonnull(AccessibilityTest::findEvent(events, "object:text-selection-changed"));
+
+    test->startEventMonitor(password.get(), { "object:text-selection-changed", "object:text-caret-moved" });
+    g_assert_true(atspi_text_set_selection(ATSPI_TEXT(password.get()), 0, 2, 3, nullptr));
+    events = test->stopEventMonitor(2);
+    g_assert_cmpuint(events.size(), ==, 2);
+    event = AccessibilityTest::findEvent(events, "object:text-caret-moved");
+    g_assert_nonnull(event);
+    g_assert_cmpuint(event->detail1, ==, 3);
+    g_assert_nonnull(AccessibilityTest::findEvent(events, "object:text-selection-changed"));
 #endif
 
     // Text changed.
@@ -1680,6 +1758,29 @@
     g_assert_true(G_VALUE_HOLDS_STRING(&events[0]->any_data));
     g_assert_cmpstr(g_value_get_string(&events[0]->any_data), ==, "n");
 
+    g_assert_true(atspi_text_set_caret_offset(ATSPI_TEXT(password.get()), 2, nullptr));
+    test->startEventMonitor(password.get(), { "object:text-changed:insert", "object:text-changed:delete" });
+    test->keyStroke(GDK_KEY_a);
+    events = test->stopEventMonitor(1);
+    g_assert_cmpstr(events[0]->type, ==, "object:text-changed:insert");
+#if USE(ATSPI)
+    g_assert_cmpuint(events[0]->detail1, ==, 2);
+#endif
+    g_assert_cmpuint(events[0]->detail2, ==, 1);
+    g_assert_true(G_VALUE_HOLDS_STRING(&events[0]->any_data));
+    g_assert_cmpstr(g_value_get_string(&events[0]->any_data), ==, "•");
+    test->startEventMonitor(password.get(), { "object:text-changed:insert", "object:text-changed:delete" });
+    test->keyStroke(GDK_KEY_BackSpace);
+    events = test->stopEventMonitor(1);
+    g_assert_cmpuint(events.size(), ==, 1);
+    g_assert_cmpstr(events[0]->type, ==, "object:text-changed:delete");
+#if USE(ATSPI)
+    g_assert_cmpuint(events[0]->detail1, ==, 2);
+#endif
+    g_assert_cmpuint(events[0]->detail2, ==, 1);
+    g_assert_true(G_VALUE_HOLDS_STRING(&events[0]->any_data));
+    g_assert_cmpstr(g_value_get_string(&events[0]->any_data), ==, "•");
+
     // Text replaced.
     g_assert_true(atspi_text_set_selection(ATSPI_TEXT(input.get()), 0, 5, 10, nullptr));
     test->startEventMonitor(input.get(), { "object:text-changed:insert", "object:text-changed:delete" });
@@ -1712,7 +1813,25 @@
     g_assert_true(G_VALUE_HOLDS_STRING(&events[0]->any_data));
     g_assert_cmpstr(g_value_get_string(&events[0]->any_data), ==, "bntet ed");
 
+    g_assert_true(atspi_text_set_selection(ATSPI_TEXT(password.get()), 0, 0, 3, nullptr));
+    test->startEventMonitor(password.get(), { "object:text-changed:insert", "object:text-changed:delete" });
+    test->keyStroke(GDK_KEY_z);
+    events = test->stopEventMonitor(2);
+    g_assert_cmpuint(events.size(), ==, 2);
+    g_assert_cmpstr(events[0]->type, ==, "object:text-changed:delete");
+    g_assert_cmpuint(events[0]->detail1, ==, 1);
+    g_assert_cmpuint(events[0]->detail2, ==, 3);
+    g_assert_true(G_VALUE_HOLDS_STRING(&events[0]->any_data));
+    g_assert_cmpstr(g_value_get_string(&events[0]->any_data), ==, "•••");
+    g_assert_cmpstr(events[1]->type, ==, "object:text-changed:insert");
 #if USE(ATSPI)
+    g_assert_cmpuint(events[1]->detail1, ==, 0);
+#endif
+    g_assert_cmpuint(events[1]->detail2, ==, 1);
+    g_assert_true(G_VALUE_HOLDS_STRING(&events[1]->any_data));
+    g_assert_cmpstr(g_value_get_string(&events[1]->any_data), ==, "•");
+
+#if USE(ATSPI)
     // Text input value changed.
     test->startEventMonitor(input.get(), { "object:text-changed:insert", "object:text-changed:delete" });
     test->runJavaScriptAndWaitUntilFinished("document.getElementsByTagName('input')[0].value = 'foo';", nullptr);
@@ -1728,6 +1847,21 @@
     g_assert_cmpuint(events[1]->detail2, ==, 3);
     g_assert_true(G_VALUE_HOLDS_STRING(&events[1]->any_data));
     g_assert_cmpstr(g_value_get_string(&events[1]->any_data), ==, "foo");
+
+    test->startEventMonitor(password.get(), { "object:text-changed:insert", "object:text-changed:delete" });
+    test->runJavaScriptAndWaitUntilFinished("document.getElementsByTagName('input')[1].value = '7890';", nullptr);
+    events = test->stopEventMonitor(2);
+    g_assert_cmpuint(events.size(), ==, 2);
+    g_assert_cmpstr(events[0]->type, ==, "object:text-changed:delete");
+    g_assert_cmpuint(events[0]->detail1, ==, 0);
+    g_assert_cmpuint(events[0]->detail2, ==, 1);
+    g_assert_true(G_VALUE_HOLDS_STRING(&events[0]->any_data));
+    g_assert_cmpstr(g_value_get_string(&events[0]->any_data), ==, "•");
+    g_assert_cmpstr(events[1]->type, ==, "object:text-changed:insert");
+    g_assert_cmpuint(events[1]->detail1, ==, 0);
+    g_assert_cmpuint(events[1]->detail2, ==, 4);
+    g_assert_true(G_VALUE_HOLDS_STRING(&events[1]->any_data));
+    g_assert_cmpstr(g_value_get_string(&events[1]->any_data), ==, "••••");
 #endif
 }
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to