<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[284675] trunk</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/284675">284675</a></dd>
<dt>Author</dt> <dd>carlosgc@webkit.org</dd>
<dt>Date</dt> <dd>2021-10-22 01:51:03 -0700 (Fri, 22 Oct 2021)</dd>
</dl>
<h3>Log Message</h3>
<pre>[GTK][a11y] Add implementation of text interface when building with ATSPI
https://bugs.webkit.org/show_bug.cgi?id=230258
Reviewed by Adrian Perez de Castro.
Source/WebCore:
* SourcesGTK.txt:
* accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::postTextStateChangeNotification):
(WebCore::AXObjectCache::postTextReplacementNotification):
(WebCore::AXObjectCache::postTextReplacementNotificationForTextControl):
(WebCore::AXObjectCache::enqueuePasswordValueChangeNotification):
* accessibility/AXObjectCache.h:
* accessibility/AccessibilityObject.cpp:
(WebCore::AccessibilityObject::textIteratorBehaviorForTextRange const):
* accessibility/AccessibilityObject.h:
* accessibility/AccessibilityRenderObject.cpp:
(WebCore::AccessibilityRenderObject::indexForVisiblePosition const):
* accessibility/atspi/AXObjectCacheAtspi.cpp:
(WebCore::AXObjectCache::postTextStateChangePlatformNotification):
(WebCore::AXObjectCache::postTextReplacementPlatformNotificationForTextControl):
(WebCore::AXObjectCache::postTextReplacementPlatformNotification):
(WebCore::AXObjectCache::nodeTextChangePlatformNotification): Deleted.
* accessibility/atspi/AccessibilityAtspi.cpp:
(WebCore::AccessibilityAtspi::textChanged):
(WebCore::AccessibilityAtspi::textAttributesChanged):
(WebCore::AccessibilityAtspi::textCaretMoved):
(WebCore::AccessibilityAtspi::textSelectionChanged):
* accessibility/atspi/AccessibilityAtspi.h:
* accessibility/atspi/AccessibilityAtspiEnums.h:
* accessibility/atspi/AccessibilityObjectAtspi.cpp:
(WebCore::roleIsTextType):
(WebCore::AccessibilityObjectAtspi::interfacesForObject):
(WebCore::AccessibilityObjectAtspi::path):
(WebCore::AccessibilityObjectAtspi::buildInterfaces const):
* accessibility/atspi/AccessibilityObjectAtspi.h:
* accessibility/atspi/AccessibilityObjectTextAtspi.cpp: Added.
(WebCore::AccessibilityObjectAtspi::atspiBoundaryToTextGranularity):
(WebCore::AccessibilityObjectAtspi::atspiGranularityToTextGranularity):
(WebCore::offsetMapping):
(WebCore::UTF16OffsetToUTF8):
(WebCore::UTF8OffsetToUTF16):
(WebCore::AccessibilityObjectAtspi::text const):
(WebCore::AccessibilityObject::getLengthForTextRange const):
(WebCore::AccessibilityObject::allowsTextRanges const):
(WebCore::AccessibilityObjectAtspi::textInserted):
(WebCore::AccessibilityObjectAtspi::textDeleted):
(WebCore::AccessibilityObjectAtspi::boundaryOffset const):
(WebCore::AccessibilityObjectAtspi::textAtOffset const):
(WebCore::AccessibilityObjectAtspi::characterAtOffset const):
(WebCore::AccessibilityObjectAtspi::boundsForRange const):
(WebCore::AccessibilityObjectAtspi::textExtents const):
(WebCore::AccessibilityObjectAtspi::offsetAtPoint const):
(WebCore::AccessibilityObjectAtspi::boundsForSelection const):
(WebCore::AccessibilityObjectAtspi::selectedRange const):
(WebCore::AccessibilityObjectAtspi::selectionBounds const):
(WebCore::AccessibilityObjectAtspi::setSelectedRange):
(WebCore::AccessibilityObjectAtspi::selectRange):
(WebCore::AccessibilityObjectAtspi::selectionChanged):
(WebCore::AccessibilityObjectAtspi::textAttributes const):
(WebCore::AccessibilityObjectAtspi::textAttributesWithUTF8Offset const):
(WebCore::AccessibilityObjectAtspi::textAttributesChanged):
(WebCore::AccessibilityObjectAtspi::scrollToMakeVisible const):
(WebCore::AccessibilityObjectAtspi::scrollToPoint const):
* editing/atspi/FrameSelectionAtspi.cpp:
(WebCore::FrameSelection::notifyAccessibilityForSelectionChange):
* html/HTMLTextFormControlElement.cpp:
(WebCore::HTMLTextFormControlElement::setInnerTextValue):
Tools:
Add unit tests for the text interface.
* TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp:
(AtspiTextRangeDeleter::operator() const):
(AccessibilityTest::startEventMonitor):
(AccessibilityTest::stopEventMonitor):
(AccessibilityTest::findEvent):
(testTextBasic):
(testTextSurrogatePair):
(testTextIterator):
(testTextExtents):
(testTextSelections):
(testTextAttributes):
(testTextStateChanged):
(beforeAll):</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreSourcesGTKtxt">trunk/Source/WebCore/SourcesGTK.txt</a></li>
<li><a href="#trunkSourceWebCoreaccessibilityAXObjectCachecpp">trunk/Source/WebCore/accessibility/AXObjectCache.cpp</a></li>
<li><a href="#trunkSourceWebCoreaccessibilityAXObjectCacheh">trunk/Source/WebCore/accessibility/AXObjectCache.h</a></li>
<li><a href="#trunkSourceWebCoreaccessibilityAccessibilityObjectcpp">trunk/Source/WebCore/accessibility/AccessibilityObject.cpp</a></li>
<li><a href="#trunkSourceWebCoreaccessibilityAccessibilityObjecth">trunk/Source/WebCore/accessibility/AccessibilityObject.h</a></li>
<li><a href="#trunkSourceWebCoreaccessibilityAccessibilityRenderObjectcpp">trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp</a></li>
<li><a href="#trunkSourceWebCoreaccessibilityatspiAXObjectCacheAtspicpp">trunk/Source/WebCore/accessibility/atspi/AXObjectCacheAtspi.cpp</a></li>
<li><a href="#trunkSourceWebCoreaccessibilityatspiAccessibilityAtspicpp">trunk/Source/WebCore/accessibility/atspi/AccessibilityAtspi.cpp</a></li>
<li><a href="#trunkSourceWebCoreaccessibilityatspiAccessibilityAtspih">trunk/Source/WebCore/accessibility/atspi/AccessibilityAtspi.h</a></li>
<li><a href="#trunkSourceWebCoreaccessibilityatspiAccessibilityAtspiEnumsh">trunk/Source/WebCore/accessibility/atspi/AccessibilityAtspiEnums.h</a></li>
<li><a href="#trunkSourceWebCoreaccessibilityatspiAccessibilityObjectAtspicpp">trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp</a></li>
<li><a href="#trunkSourceWebCoreaccessibilityatspiAccessibilityObjectAtspih">trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.h</a></li>
<li><a href="#trunkSourceWebCoreeditingatspiFrameSelectionAtspicpp">trunk/Source/WebCore/editing/atspi/FrameSelectionAtspi.cpp</a></li>
<li><a href="#trunkSourceWebCorehtmlHTMLTextFormControlElementcpp">trunk/Source/WebCore/html/HTMLTextFormControlElement.cpp</a></li>
<li><a href="#trunkToolsChangeLog">trunk/Tools/ChangeLog</a></li>
<li><a href="#trunkToolsTestWebKitAPITestsWebKitGtkTestWebKitAccessibilitycpp">trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#trunkSourceWebCoreaccessibilityatspiAccessibilityObjectTextAtspicpp">trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTextAtspi.cpp</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/ChangeLog 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -1,3 +1,73 @@
</span><ins>+2021-10-22 Carlos Garcia Campos <cgarcia@igalia.com>
+
+ [GTK][a11y] Add implementation of text interface when building with ATSPI
+ https://bugs.webkit.org/show_bug.cgi?id=230258
+
+ Reviewed by Adrian Perez de Castro.
+
+ * SourcesGTK.txt:
+ * accessibility/AXObjectCache.cpp:
+ (WebCore::AXObjectCache::postTextStateChangeNotification):
+ (WebCore::AXObjectCache::postTextReplacementNotification):
+ (WebCore::AXObjectCache::postTextReplacementNotificationForTextControl):
+ (WebCore::AXObjectCache::enqueuePasswordValueChangeNotification):
+ * accessibility/AXObjectCache.h:
+ * accessibility/AccessibilityObject.cpp:
+ (WebCore::AccessibilityObject::textIteratorBehaviorForTextRange const):
+ * accessibility/AccessibilityObject.h:
+ * accessibility/AccessibilityRenderObject.cpp:
+ (WebCore::AccessibilityRenderObject::indexForVisiblePosition const):
+ * accessibility/atspi/AXObjectCacheAtspi.cpp:
+ (WebCore::AXObjectCache::postTextStateChangePlatformNotification):
+ (WebCore::AXObjectCache::postTextReplacementPlatformNotificationForTextControl):
+ (WebCore::AXObjectCache::postTextReplacementPlatformNotification):
+ (WebCore::AXObjectCache::nodeTextChangePlatformNotification): Deleted.
+ * accessibility/atspi/AccessibilityAtspi.cpp:
+ (WebCore::AccessibilityAtspi::textChanged):
+ (WebCore::AccessibilityAtspi::textAttributesChanged):
+ (WebCore::AccessibilityAtspi::textCaretMoved):
+ (WebCore::AccessibilityAtspi::textSelectionChanged):
+ * accessibility/atspi/AccessibilityAtspi.h:
+ * accessibility/atspi/AccessibilityAtspiEnums.h:
+ * accessibility/atspi/AccessibilityObjectAtspi.cpp:
+ (WebCore::roleIsTextType):
+ (WebCore::AccessibilityObjectAtspi::interfacesForObject):
+ (WebCore::AccessibilityObjectAtspi::path):
+ (WebCore::AccessibilityObjectAtspi::buildInterfaces const):
+ * accessibility/atspi/AccessibilityObjectAtspi.h:
+ * accessibility/atspi/AccessibilityObjectTextAtspi.cpp: Added.
+ (WebCore::AccessibilityObjectAtspi::atspiBoundaryToTextGranularity):
+ (WebCore::AccessibilityObjectAtspi::atspiGranularityToTextGranularity):
+ (WebCore::offsetMapping):
+ (WebCore::UTF16OffsetToUTF8):
+ (WebCore::UTF8OffsetToUTF16):
+ (WebCore::AccessibilityObjectAtspi::text const):
+ (WebCore::AccessibilityObject::getLengthForTextRange const):
+ (WebCore::AccessibilityObject::allowsTextRanges const):
+ (WebCore::AccessibilityObjectAtspi::textInserted):
+ (WebCore::AccessibilityObjectAtspi::textDeleted):
+ (WebCore::AccessibilityObjectAtspi::boundaryOffset const):
+ (WebCore::AccessibilityObjectAtspi::textAtOffset const):
+ (WebCore::AccessibilityObjectAtspi::characterAtOffset const):
+ (WebCore::AccessibilityObjectAtspi::boundsForRange const):
+ (WebCore::AccessibilityObjectAtspi::textExtents const):
+ (WebCore::AccessibilityObjectAtspi::offsetAtPoint const):
+ (WebCore::AccessibilityObjectAtspi::boundsForSelection const):
+ (WebCore::AccessibilityObjectAtspi::selectedRange const):
+ (WebCore::AccessibilityObjectAtspi::selectionBounds const):
+ (WebCore::AccessibilityObjectAtspi::setSelectedRange):
+ (WebCore::AccessibilityObjectAtspi::selectRange):
+ (WebCore::AccessibilityObjectAtspi::selectionChanged):
+ (WebCore::AccessibilityObjectAtspi::textAttributes const):
+ (WebCore::AccessibilityObjectAtspi::textAttributesWithUTF8Offset const):
+ (WebCore::AccessibilityObjectAtspi::textAttributesChanged):
+ (WebCore::AccessibilityObjectAtspi::scrollToMakeVisible const):
+ (WebCore::AccessibilityObjectAtspi::scrollToPoint const):
+ * editing/atspi/FrameSelectionAtspi.cpp:
+ (WebCore::FrameSelection::notifyAccessibilityForSelectionChange):
+ * html/HTMLTextFormControlElement.cpp:
+ (WebCore::HTMLTextFormControlElement::setInnerTextValue):
+
</ins><span class="cx"> 2021-10-22 Youenn Fablet <youenn@apple.com>
</span><span class="cx">
</span><span class="cx"> Audio over peer connection becomes latent when changing the output
</span></span></pre></div>
<a id="trunkSourceWebCoreSourcesGTKtxt"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/SourcesGTK.txt (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/SourcesGTK.txt 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/SourcesGTK.txt 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -42,6 +42,7 @@
</span><span class="cx"> accessibility/atspi/AccessibilityAtspi.cpp
</span><span class="cx"> accessibility/atspi/AccessibilityObjectAtspi.cpp
</span><span class="cx"> accessibility/atspi/AccessibilityObjectComponentAtspi.cpp
</span><ins>+accessibility/atspi/AccessibilityObjectTextAtspi.cpp
</ins><span class="cx"> accessibility/atspi/AccessibilityRootAtspi.cpp
</span><span class="cx"> accessibility/atspi/AXObjectCacheAtspi.cpp
</span><span class="cx">
</span></span></pre></div>
<a id="trunkSourceWebCoreaccessibilityAXObjectCachecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/accessibility/AXObjectCache.cpp (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/accessibility/AXObjectCache.cpp 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.cpp 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -1409,7 +1409,7 @@
</span><span class="cx"> if (!node)
</span><span class="cx"> return;
</span><span class="cx">
</span><del>-#if PLATFORM(COCOA)
</del><ins>+#if PLATFORM(COCOA) || USE(ATSPI)
</ins><span class="cx"> stopCachingComputedObjectAttributes();
</span><span class="cx">
</span><span class="cx"> postTextStateChangeNotification(getOrCreate(node), intent, selection);
</span><span class="lines">@@ -1428,9 +1428,10 @@
</span><span class="cx">
</span><span class="cx"> stopCachingComputedObjectAttributes();
</span><span class="cx">
</span><del>-#if PLATFORM(COCOA)
</del><ins>+#if PLATFORM(COCOA) || USE(ATSPI)
</ins><span class="cx"> AccessibilityObject* object = getOrCreate(node);
</span><span class="cx"> if (object && object->accessibilityIsIgnored()) {
</span><ins>+#if PLATFORM(COCOA)
</ins><span class="cx"> if (position.atLastEditingPositionForNode()) {
</span><span class="cx"> if (AccessibilityObject* nextSibling = object->nextSiblingUnignored(1))
</span><span class="cx"> object = nextSibling;
</span><span class="lines">@@ -1438,6 +1439,13 @@
</span><span class="cx"> if (AccessibilityObject* previousSibling = object->previousSiblingUnignored(1))
</span><span class="cx"> object = previousSibling;
</span><span class="cx"> }
</span><ins>+#elif USE(ATSPI)
+ // ATSPI doesn't expose text nodes, so we need the parent
+ // object which is the one implementing the text interface.
+ auto* parent = object->parentObjectUnignored();
+ if (is<AccessibilityObject>(parent))
+ object = downcast<AccessibilityObject>(parent);
+#endif
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> postTextStateChangeNotification(object, intent, selection);
</span><span class="lines">@@ -1451,7 +1459,7 @@
</span><span class="cx"> AXTRACE("AXObjectCache::postTextStateChangeNotification");
</span><span class="cx"> stopCachingComputedObjectAttributes();
</span><span class="cx">
</span><del>-#if PLATFORM(COCOA)
</del><ins>+#if PLATFORM(COCOA) || USE(ATSPI)
</ins><span class="cx"> if (object) {
</span><span class="cx"> if (isPasswordFieldOrContainedByPasswordField(object))
</span><span class="cx"> return;
</span><span class="lines">@@ -1490,7 +1498,7 @@
</span><span class="cx"> stopCachingComputedObjectAttributes();
</span><span class="cx">
</span><span class="cx"> AccessibilityObject* object = getOrCreate(node);
</span><del>-#if PLATFORM(COCOA)
</del><ins>+#if PLATFORM(COCOA) || USE(ATSPI)
</ins><span class="cx"> if (object) {
</span><span class="cx"> if (enqueuePasswordValueChangeNotification(object))
</span><span class="cx"> return;
</span><span class="lines">@@ -1525,7 +1533,7 @@
</span><span class="cx"> stopCachingComputedObjectAttributes();
</span><span class="cx">
</span><span class="cx"> AccessibilityObject* object = getOrCreate(node);
</span><del>-#if PLATFORM(COCOA)
</del><ins>+#if PLATFORM(COCOA) || USE(ATSPI)
</ins><span class="cx"> if (object) {
</span><span class="cx"> if (enqueuePasswordValueChangeNotification(object))
</span><span class="cx"> return;
</span><span class="lines">@@ -1544,7 +1552,7 @@
</span><span class="cx"> stopCachingComputedObjectAttributes();
</span><span class="cx">
</span><span class="cx"> AccessibilityObject* object = getOrCreate(&textControl);
</span><del>-#if PLATFORM(COCOA)
</del><ins>+#if PLATFORM(COCOA) || USE(ATSPI)
</ins><span class="cx"> if (object) {
</span><span class="cx"> if (enqueuePasswordValueChangeNotification(object))
</span><span class="cx"> return;
</span><span class="lines">@@ -1560,6 +1568,7 @@
</span><span class="cx">
</span><span class="cx"> bool AXObjectCache::enqueuePasswordValueChangeNotification(AccessibilityObject* object)
</span><span class="cx"> {
</span><ins>+#if PLATFORM(COCOA)
</ins><span class="cx"> if (!isPasswordFieldOrContainedByPasswordField(object))
</span><span class="cx"> return false;
</span><span class="cx">
</span><span class="lines">@@ -1575,6 +1584,10 @@
</span><span class="cx"> m_passwordNotificationPostTimer.startOneShot(accessibilityPasswordValueChangeNotificationInterval);
</span><span class="cx">
</span><span class="cx"> return true;
</span><ins>+#else
+ UNUSED_PARAM(object);
+ return false;
+#endif
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> void AXObjectCache::frameLoadingEventNotification(Frame* frame, AXLoadingEvent loadingEvent)
</span><span class="lines">@@ -3463,7 +3476,7 @@
</span><span class="cx"> m_cache->stopCachingComputedObjectAttributes();
</span><span class="cx"> }
</span><span class="cx">
</span><del>-#if !PLATFORM(COCOA)
</del><ins>+#if !PLATFORM(COCOA) && !USE(ATSPI)
</ins><span class="cx"> AXTextChange AXObjectCache::textChangeForEditType(AXTextEditType type)
</span><span class="cx"> {
</span><span class="cx"> switch (type) {
</span></span></pre></div>
<a id="trunkSourceWebCoreaccessibilityAXObjectCacheh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/accessibility/AXObjectCache.h (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/accessibility/AXObjectCache.h 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.h 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -398,7 +398,7 @@
</span><span class="cx">
</span><span class="cx"> void platformPerformDeferredCacheUpdate();
</span><span class="cx">
</span><del>-#if PLATFORM(COCOA)
</del><ins>+#if PLATFORM(COCOA) || USE(ATSPI)
</ins><span class="cx"> void postTextStateChangePlatformNotification(AXCoreObject*, const AXTextStateChangeIntent&, const VisibleSelection&);
</span><span class="cx"> void postTextStateChangePlatformNotification(AccessibilityObject*, AXTextEditType, const String&, const VisiblePosition&);
</span><span class="cx"> void postTextReplacementPlatformNotificationForTextControl(AXCoreObject*, const String& deletedText, const String& insertedText, HTMLTextFormControlElement&);
</span></span></pre></div>
<a id="trunkSourceWebCoreaccessibilityAccessibilityObjectcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/accessibility/AccessibilityObject.cpp (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/accessibility/AccessibilityObject.cpp 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/accessibility/AccessibilityObject.cpp 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -3273,8 +3273,8 @@
</span><span class="cx"> TextIteratorBehaviors AccessibilityObject::textIteratorBehaviorForTextRange() const
</span><span class="cx"> {
</span><span class="cx"> TextIteratorBehaviors behaviors { TextIteratorBehavior::IgnoresStyleVisibility };
</span><del>-
-#if USE(ATK)
</del><ins>+
+#if USE(ATK) || USE(ATSPI)
</ins><span class="cx"> // We need to emit replaced elements for GTK, and present
</span><span class="cx"> // them with the 'object replacement character' (0xFFFC).
</span><span class="cx"> behaviors.add(TextIteratorBehavior::EmitsObjectReplacementCharacters);
</span></span></pre></div>
<a id="trunkSourceWebCoreaccessibilityAccessibilityObjecth"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/accessibility/AccessibilityObject.h (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/accessibility/AccessibilityObject.h 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/accessibility/AccessibilityObject.h 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -833,7 +833,7 @@
</span><span class="cx"> inline void AccessibilityObject::detachPlatformWrapper(AccessibilityDetachmentType) { }
</span><span class="cx"> #endif
</span><span class="cx">
</span><del>-#if !(ENABLE(ACCESSIBILITY) && USE(ATK))
</del><ins>+#if !(ENABLE(ACCESSIBILITY) && (USE(ATK) || USE(ATSPI)))
</ins><span class="cx"> inline bool AccessibilityObject::allowsTextRanges() const { return true; }
</span><span class="cx"> inline unsigned AccessibilityObject::getLengthForTextRange() const { return text().length(); }
</span><span class="cx"> #endif
</span></span></pre></div>
<a id="trunkSourceWebCoreaccessibilityAccessibilityRenderObjectcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/accessibility/AccessibilityRenderObject.cpp 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -2160,7 +2160,7 @@
</span><span class="cx"> if (!node)
</span><span class="cx"> return 0;
</span><span class="cx">
</span><del>-#if USE(ATK)
</del><ins>+#if USE(ATK) || USE(ATSPI)
</ins><span class="cx"> // We need to consider replaced elements for GTK, as they will be
</span><span class="cx"> // presented with the 'object replacement character' (0xFFFC).
</span><span class="cx"> bool forSelectionPreservation = true;
</span></span></pre></div>
<a id="trunkSourceWebCoreaccessibilityatspiAXObjectCacheAtspicpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/accessibility/atspi/AXObjectCacheAtspi.cpp (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/accessibility/atspi/AXObjectCacheAtspi.cpp 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/accessibility/atspi/AXObjectCacheAtspi.cpp 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -21,6 +21,7 @@
</span><span class="cx"> #include "AXObjectCache.h"
</span><span class="cx">
</span><span class="cx"> #if ENABLE(ACCESSIBILITY) && USE(ATSPI)
</span><ins>+#include "AXTextStateChangeIntent.h"
</ins><span class="cx"> #include "AccessibilityObject.h"
</span><span class="cx"> #include "AccessibilityObjectAtspi.h"
</span><span class="cx"> #include "AccessibilityRenderObject.h"
</span><span class="lines">@@ -185,10 +186,95 @@
</span><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx">
</span><del>-void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject* object, AXTextChange textChange, unsigned offset, const String& text)
</del><ins>+void AXObjectCache::postTextStateChangePlatformNotification(AXCoreObject* coreObject, const AXTextStateChangeIntent&, const VisibleSelection& selection)
</ins><span class="cx"> {
</span><ins>+ RELEASE_ASSERT(isMainThread());
+ if (!coreObject)
+ coreObject = rootWebArea();
+
+ if (!coreObject)
+ return;
+
+ auto* wrapper = coreObject->wrapper();
+ if (!wrapper)
+ return;
+
+ wrapper->selectionChanged(selection);
</ins><span class="cx"> }
</span><span class="cx">
</span><ins>+void AXObjectCache::postTextStateChangePlatformNotification(AccessibilityObject* coreObject, AXTextEditType editType, const String& text, const VisiblePosition& position)
+{
+ RELEASE_ASSERT(isMainThread());
+ if (text.isEmpty())
+ return;
+
+ auto* wrapper = coreObject->wrapper();
+ if (!wrapper)
+ return;
+
+ switch (editType) {
+ case AXTextEditTypeDelete:
+ case AXTextEditTypeCut:
+ wrapper->textDeleted(text, position);
+ break;
+ case AXTextEditTypeInsert:
+ case AXTextEditTypeTyping:
+ case AXTextEditTypeDictation:
+ case AXTextEditTypePaste:
+ wrapper->textInserted(text, position);
+ break;
+ case AXTextEditTypeAttributesChange:
+ wrapper->textAttributesChanged();
+ break;
+ case AXTextEditTypeUnknown:
+ break;
+ }
+}
+
+void AXObjectCache::postTextReplacementPlatformNotificationForTextControl(AXCoreObject* coreObject, const String& deletedText, const String& insertedText, HTMLTextFormControlElement&)
+{
+ RELEASE_ASSERT(isMainThread());
+ if (!coreObject)
+ coreObject = rootWebArea();
+
+ if (!coreObject)
+ return;
+
+ if (deletedText.isEmpty() && insertedText.isEmpty())
+ return;
+
+ auto* wrapper = coreObject->wrapper();
+ if (!wrapper)
+ return;
+
+ if (!deletedText.isEmpty())
+ wrapper->textDeleted(deletedText, coreObject->visiblePositionForIndex(0));
+ if (!insertedText.isEmpty())
+ wrapper->textInserted(insertedText, coreObject->visiblePositionForIndex(insertedText.length()));
+}
+
+void AXObjectCache::postTextReplacementPlatformNotification(AXCoreObject* coreObject, AXTextEditType, const String& deletedText, AXTextEditType, const String& insertedText, const VisiblePosition& position)
+{
+ RELEASE_ASSERT(isMainThread());
+ if (!coreObject)
+ coreObject = rootWebArea();
+
+ if (!coreObject)
+ return;
+
+ if (deletedText.isEmpty() && insertedText.isEmpty())
+ return;
+
+ auto* wrapper = coreObject->wrapper();
+ if (!wrapper)
+ return;
+
+ if (!deletedText.isEmpty())
+ wrapper->textDeleted(deletedText, position);
+ if (!insertedText.isEmpty())
+ wrapper->textInserted(insertedText, position);
+}
+
</ins><span class="cx"> void AXObjectCache::frameLoadingEventPlatformNotification(AccessibilityObject* object, AXLoadingEvent loadingEvent)
</span><span class="cx"> {
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebCoreaccessibilityatspiAccessibilityAtspicpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/accessibility/atspi/AccessibilityAtspi.cpp (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/accessibility/atspi/AccessibilityAtspi.cpp 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityAtspi.cpp 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -167,6 +167,54 @@
</span><span class="cx"> });
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+void AccessibilityAtspi::textChanged(AccessibilityObjectAtspi& atspiObject, const char* changeType, CString&& text, unsigned offset, unsigned length)
+{
+ RELEASE_ASSERT(isMainThread());
+ m_queue->dispatch([this, atspiObject = Ref { atspiObject }, changeType = CString(changeType), text = WTFMove(text), offset, length] {
+ if (!m_connection)
+ return;
+
+ g_dbus_connection_emit_signal(m_connection.get(), nullptr, atspiObject->path().utf8().data(), "org.a11y.atspi.Event.Object", "TextChanged",
+ g_variant_new("(siiva{sv})", changeType.data(), offset, length, g_variant_new_string(text.data()), nullptr), nullptr);
+ });
+}
+
+void AccessibilityAtspi::textAttributesChanged(AccessibilityObjectAtspi& atspiObject)
+{
+ RELEASE_ASSERT(isMainThread());
+ m_queue->dispatch([this, atspiObject = Ref { atspiObject }] {
+ if (!m_connection)
+ return;
+
+ g_dbus_connection_emit_signal(m_connection.get(), nullptr, atspiObject->path().utf8().data(), "org.a11y.atspi.Event.Object", "TextAttributesChanged",
+ g_variant_new("(siiva{sv})", "", 0, 0, g_variant_new_string(""), nullptr), nullptr);
+ });
+}
+
+void AccessibilityAtspi::textCaretMoved(AccessibilityObjectAtspi& atspiObject, unsigned caretOffset)
+{
+ RELEASE_ASSERT(isMainThread());
+ m_queue->dispatch([this, atspiObject = Ref { atspiObject }, caretOffset] {
+ if (!m_connection)
+ return;
+
+ g_dbus_connection_emit_signal(m_connection.get(), nullptr, atspiObject->path().utf8().data(), "org.a11y.atspi.Event.Object", "TextCaretMoved",
+ g_variant_new("(siiva{sv})", "", caretOffset, 0, g_variant_new_string(""), nullptr), nullptr);
+ });
+}
+
+void AccessibilityAtspi::textSelectionChanged(AccessibilityObjectAtspi& atspiObject)
+{
+ RELEASE_ASSERT(isMainThread());
+ m_queue->dispatch([this, atspiObject = Ref { atspiObject }] {
+ if (!m_connection)
+ return;
+
+ g_dbus_connection_emit_signal(m_connection.get(), nullptr, atspiObject->path().utf8().data(), "org.a11y.atspi.Event.Object", "TextSelectionChanged",
+ g_variant_new("(siiva{sv})", "", 0, 0, g_variant_new_string(""), nullptr), nullptr);
+ });
+}
+
</ins><span class="cx"> struct RoleNameEntry {
</span><span class="cx"> const char* name;
</span><span class="cx"> const char* localizedName;
</span></span></pre></div>
<a id="trunkSourceWebCoreaccessibilityatspiAccessibilityAtspih"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/accessibility/atspi/AccessibilityAtspi.h (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/accessibility/atspi/AccessibilityAtspi.h 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityAtspi.h 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -56,6 +56,11 @@
</span><span class="cx">
</span><span class="cx"> void stateChanged(AccessibilityObjectAtspi&, const char*, bool);
</span><span class="cx">
</span><ins>+ void textChanged(AccessibilityObjectAtspi&, const char*, CString&&, unsigned, unsigned);
+ void textAttributesChanged(AccessibilityObjectAtspi&);
+ void textCaretMoved(AccessibilityObjectAtspi&, unsigned);
+ void textSelectionChanged(AccessibilityObjectAtspi&);
+
</ins><span class="cx"> static const char* localizedRoleName(AccessibilityRole);
</span><span class="cx">
</span><span class="cx"> private:
</span></span></pre></div>
<a id="trunkSourceWebCoreaccessibilityatspiAccessibilityAtspiEnumsh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/accessibility/atspi/AccessibilityAtspiEnums.h (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/accessibility/atspi/AccessibilityAtspiEnums.h 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityAtspiEnums.h 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -258,6 +258,24 @@
</span><span class="cx"> Anywhere
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+enum TextBoundaryType {
+ CharBoundary,
+ WordStartBoundary,
+ WordEndBoundary,
+ SentenceStartBoundary,
+ SentenceEndBoundary,
+ LineStartBoundary,
+ LineEndBoundary
+};
+
+enum TextGranularityType {
+ CharGranularity,
+ WordGranularity,
+ SentenceGranularity,
+ LineGranularity,
+ ParagraphGranularity
+};
+
</ins><span class="cx"> } // namespace Atspi
</span><span class="cx"> } // namespace WebCore
</span><span class="cx">
</span></span></pre></div>
<a id="trunkSourceWebCoreaccessibilityatspiAccessibilityObjectAtspicpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.cpp 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -21,6 +21,7 @@
</span><span class="cx"> #include "AccessibilityObjectAtspi.h"
</span><span class="cx">
</span><span class="cx"> #if ENABLE(ACCESSIBILITY) && USE(ATSPI)
</span><ins>+#include "AXIsolatedObject.h"
</ins><span class="cx"> #include "AccessibilityAtspiEnums.h"
</span><span class="cx"> #include "AccessibilityObjectInterface.h"
</span><span class="cx"> #include "AccessibilityRootAtspi.h"
</span><span class="lines">@@ -28,6 +29,7 @@
</span><span class="cx"> #include "ElementInlines.h"
</span><span class="cx"> #include "RenderAncestorIterator.h"
</span><span class="cx"> #include "RenderBlock.h"
</span><ins>+#include "RenderObject.h"
</ins><span class="cx"> #include <glib/gi18n-lib.h>
</span><span class="cx"> #include <wtf/MainThread.h>
</span><span class="cx"> #include <wtf/UUID.h>
</span><span class="lines">@@ -39,10 +41,48 @@
</span><span class="cx"> return adoptRef(*new AccessibilityObjectAtspi(coreObject));
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+static inline bool roleIsTextType(AccessibilityRole role)
+{
+ return role == AccessibilityRole::Paragraph
+ || role == AccessibilityRole::Heading
+ || role == AccessibilityRole::Div
+ || role == AccessibilityRole::Cell
+ || role == AccessibilityRole::Link
+ || role == AccessibilityRole::WebCoreLink
+ || role == AccessibilityRole::ListItem
+ || role == AccessibilityRole::Pre
+ || role == AccessibilityRole::GridCell
+ || role == AccessibilityRole::TextGroup
+ || role == AccessibilityRole::ApplicationTextGroup
+ || role == AccessibilityRole::ApplicationGroup;
+}
+
</ins><span class="cx"> OptionSet<AccessibilityObjectAtspi::Interface> AccessibilityObjectAtspi::interfacesForObject(AXCoreObject& coreObject)
</span><span class="cx"> {
</span><span class="cx"> OptionSet<Interface> interfaces = { Interface::Accessible, Interface::Component };
</span><span class="cx">
</span><ins>+ RenderObject* renderer = coreObject.isAccessibilityRenderObject() ? coreObject.renderer() : nullptr;
+ if (coreObject.roleValue() == AccessibilityRole::StaticText || coreObject.roleValue() == AccessibilityRole::ColorWell)
+ interfaces.add(Interface::Text);
+ else if (coreObject.isTextControl() || coreObject.isNonNativeTextControl())
+ interfaces.add(Interface::Text);
+ else if (!coreObject.isWebArea()) {
+ if (coreObject.roleValue() != AccessibilityRole::Table) {
+ 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);
+ }
+ }
+ }
+
</ins><span class="cx"> return interfaces;
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -427,6 +467,8 @@
</span><span class="cx"> interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_accessible_interface), &s_accessibleFunctions });
</span><span class="cx"> if (m_interfaces.contains(Interface::Component))
</span><span class="cx"> interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_component_interface), &s_componentFunctions });
</span><ins>+ if (m_interfaces.contains(Interface::Text))
+ interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_text_interface), &s_textFunctions });
</ins><span class="cx"> m_path = atspiRoot->atspi().registerObject(*this, WTFMove(interfaces));
</span><span class="cx"> }
</span><span class="cx">
</span><span class="lines">@@ -1040,6 +1082,8 @@
</span><span class="cx"> g_variant_builder_add(builder, "s", webkit_accessible_interface.name);
</span><span class="cx"> if (m_interfaces.contains(Interface::Component))
</span><span class="cx"> g_variant_builder_add(builder, "s", webkit_component_interface.name);
</span><ins>+ if (m_interfaces.contains(Interface::Text))
+ g_variant_builder_add(builder, "s", webkit_text_interface.name);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> void AccessibilityObjectAtspi::serialize(GVariantBuilder* builder) const
</span></span></pre></div>
<a id="trunkSourceWebCoreaccessibilityatspiAccessibilityObjectAtspih"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.h (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.h 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectAtspi.h 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -44,7 +44,8 @@
</span><span class="cx">
</span><span class="cx"> enum class Interface : uint8_t {
</span><span class="cx"> Accessible = 1 << 0,
</span><del>- Component = 1 << 1
</del><ins>+ Component = 1 << 1,
+ Text = 1 << 2
</ins><span class="cx"> };
</span><span class="cx"> const OptionSet<Interface>& interfaces() const { return m_interfaces; }
</span><span class="cx">
</span><span class="lines">@@ -82,6 +83,32 @@
</span><span class="cx"> void scrollToMakeVisible(uint32_t) const;
</span><span class="cx"> void scrollToPoint(const IntPoint&, uint32_t) const;
</span><span class="cx">
</span><ins>+ String text() const;
+ enum class TextGranularity {
+ Character,
+ WordStart,
+ WordEnd,
+ SentenceStart,
+ SentenceEnd,
+ LineStart,
+ LineEnd,
+ Paragraph
+ };
+ IntPoint boundaryOffset(unsigned, TextGranularity) const;
+ IntRect boundsForRange(unsigned, unsigned, uint32_t) const;
+ struct TextAttributes {
+ HashMap<String, String> attributes;
+ int startOffset;
+ int endOffset;
+ };
+ TextAttributes textAttributes(std::optional<unsigned> = std::nullopt, bool = false) const;
+ IntPoint selectedRange() const;
+ void setSelectedRange(unsigned, unsigned);
+ void textInserted(const String&, const VisiblePosition&);
+ void textDeleted(const String&, const VisiblePosition&);
+ void textAttributesChanged();
+ void selectionChanged(const VisibleSelection&);
+
</ins><span class="cx"> private:
</span><span class="cx"> explicit AccessibilityObjectAtspi(AXCoreObject*);
</span><span class="cx">
</span><span class="lines">@@ -99,10 +126,25 @@
</span><span class="cx"> bool focus() const;
</span><span class="cx"> float opacity() const;
</span><span class="cx">
</span><ins>+ static TextGranularity atspiBoundaryToTextGranularity(uint32_t);
+ static TextGranularity atspiGranularityToTextGranularity(uint32_t);
+ CString text(int, int) const;
+ CString textAtOffset(int, TextGranularity, int&, int&) const;
+ int characterAtOffset(int) const;
+ IntRect textExtents(int, int, uint32_t) const;
+ int offsetAtPoint(const IntPoint&, uint32_t) const;
+ IntPoint boundsForSelection(const VisibleSelection&) const;
+ bool selectionBounds(int&, int&) const;
+ bool selectRange(int, int);
+ TextAttributes textAttributesWithUTF8Offset(std::optional<int> = std::nullopt, bool = false) const;
+ bool scrollToMakeVisible(int, int, uint32_t) const;
+ bool scrollToPoint(int, int, uint32_t, int, int) const;
+
</ins><span class="cx"> static OptionSet<Interface> interfacesForObject(AXCoreObject&);
</span><span class="cx">
</span><span class="cx"> static GDBusInterfaceVTable s_accessibleFunctions;
</span><span class="cx"> static GDBusInterfaceVTable s_componentFunctions;
</span><ins>+ static GDBusInterfaceVTable s_textFunctions;
</ins><span class="cx">
</span><span class="cx"> AXCoreObject* m_axObject { nullptr };
</span><span class="cx"> AXCoreObject* m_coreObject { nullptr };
</span></span></pre></div>
<a id="trunkSourceWebCoreaccessibilityatspiAccessibilityObjectTextAtspicpp"></a>
<div class="addfile"><h4>Added: trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTextAtspi.cpp (0 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTextAtspi.cpp (rev 0)
+++ trunk/Source/WebCore/accessibility/atspi/AccessibilityObjectTextAtspi.cpp 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -0,0 +1,1009 @@
</span><ins>+/*
+ * 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 "AccessibilityObjectInterface.h"
+#include "Editing.h"
+#include "PlatformScreen.h"
+#include "SurrogatePairAwareTextIterator.h"
+#include "TextIterator.h"
+#include "VisibleUnits.h"
+
+namespace WebCore {
+
+AccessibilityObjectAtspi::TextGranularity AccessibilityObjectAtspi::atspiBoundaryToTextGranularity(uint32_t boundaryType)
+{
+ switch (boundaryType) {
+ case Atspi::TextBoundaryType::CharBoundary:
+ return TextGranularity::Character;
+ case Atspi::TextBoundaryType::WordStartBoundary:
+ return TextGranularity::WordStart;
+ case Atspi::TextBoundaryType::WordEndBoundary:
+ return TextGranularity::WordEnd;
+ case Atspi::TextBoundaryType::SentenceStartBoundary:
+ return TextGranularity::SentenceStart;
+ case Atspi::TextBoundaryType::SentenceEndBoundary:
+ return TextGranularity::SentenceEnd;
+ case Atspi::TextBoundaryType::LineStartBoundary:
+ return TextGranularity::LineStart;
+ case Atspi::TextBoundaryType::LineEndBoundary:
+ return TextGranularity::LineEnd;
+ }
+ RELEASE_ASSERT_NOT_REACHED();
+}
+
+AccessibilityObjectAtspi::TextGranularity AccessibilityObjectAtspi::atspiGranularityToTextGranularity(uint32_t boundaryType)
+{
+ switch (boundaryType) {
+ case Atspi::TextGranularityType::CharGranularity:
+ return TextGranularity::Character;
+ case Atspi::TextGranularityType::WordGranularity:
+ return TextGranularity::WordStart;
+ case Atspi::TextGranularityType::SentenceGranularity:
+ return TextGranularity::SentenceStart;
+ case Atspi::TextGranularityType::LineGranularity:
+ return TextGranularity::LineStart;
+ case Atspi::TextGranularityType::ParagraphGranularity:
+ return TextGranularity::Paragraph;
+ }
+ RELEASE_ASSERT_NOT_REACHED();
+}
+
+GDBusInterfaceVTable AccessibilityObjectAtspi::s_textFunctions = {
+ // 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, "GetStringAtOffset")) {
+ int offset;
+ uint32_t granularityType;
+ g_variant_get(parameters, "(iu)", &offset, &granularityType);
+ int start = 0, end = 0;
+ auto text = atspiObject->textAtOffset(offset, atspiGranularityToTextGranularity(granularityType), start, end);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(sii)", text.isNull() ? "" : text.data(), start, end));
+ } else if (!g_strcmp0(methodName, "GetText")) {
+ int start, end;
+ g_variant_get(parameters, "(ii)", &start, &end);
+ auto text = atspiObject->text(start, end);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", text.isNull() ? "" : text.data()));
+ } else if (!g_strcmp0(methodName, "SetCaretOffset")) {
+ int offset;
+ g_variant_get(parameters, "(i)", &offset);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", atspiObject->selectRange(offset, offset)));
+ } else if (!g_strcmp0(methodName, "GetTextBeforeOffset")) {
+ g_dbus_method_invocation_return_error_literal(invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ } else if (!g_strcmp0(methodName, "GetTextAtOffset")) {
+ int offset;
+ uint32_t boundaryType;
+ g_variant_get(parameters, "(iu)", &offset, &boundaryType);
+ int start = 0, end = 0;
+ auto text = atspiObject->textAtOffset(offset, atspiBoundaryToTextGranularity(boundaryType), start, end);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(sii)", text.isNull() ? "" : text.data(), start, end));
+ } else if (!g_strcmp0(methodName, "GetTextAfterOffset"))
+ g_dbus_method_invocation_return_error_literal(invocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED, "");
+ else if (!g_strcmp0(methodName, "GetCharacterAtOffset")) {
+ int offset;
+ g_variant_get(parameters, "(i)", &offset);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", atspiObject->characterAtOffset(offset)));
+ } else if (!g_strcmp0(methodName, "GetAttributeValue")) {
+ int offset;
+ const char* name;
+ g_variant_get(parameters, "(i&s)", &offset, &name);
+ auto attributes = atspiObject->textAttributesWithUTF8Offset(offset);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", attributes.attributes.get(String::fromUTF8(name)).utf8().data()));
+ } else if (!g_strcmp0(methodName, "GetAttributes")) {
+ int offset;
+ g_variant_get(parameters, "(i)", &offset);
+ auto attributes = atspiObject->textAttributesWithUTF8Offset(offset);
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("a{ss}"));
+ for (const auto& it : attributes.attributes)
+ g_variant_builder_add(&builder, "{ss}", it.key.utf8().data(), it.value.utf8().data());
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(a{ss}ii)", &builder, attributes.startOffset, attributes.endOffset));
+ } else if (!g_strcmp0(methodName, "GetAttributeRun")) {
+ int offset;
+ gboolean includeDefaults;
+ g_variant_get(parameters, "(ib)", &offset, &includeDefaults);
+ auto attributes = atspiObject->textAttributesWithUTF8Offset(offset, includeDefaults);
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("a{ss}"));
+ for (const auto& it : attributes.attributes)
+ g_variant_builder_add(&builder, "{ss}", it.key.utf8().data(), it.value.utf8().data());
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(a{ss}ii)", &builder, attributes.startOffset, attributes.endOffset));
+ } else if (!g_strcmp0(methodName, "GetDefaultAttributes") || !g_strcmp0(methodName, "GetDefaultAttributeSet")) {
+ auto attributes = atspiObject->textAttributesWithUTF8Offset();
+ GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("a{ss}"));
+ for (const auto& it : attributes.attributes)
+ g_variant_builder_add(&builder, "{ss}", it.key.utf8().data(), it.value.utf8().data());
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(a{ss})", &builder));
+ } else if (!g_strcmp0(methodName, "GetCharacterExtents")) {
+ int offset;
+ uint32_t coordinateType;
+ g_variant_get(parameters, "(iu)", &offset, &coordinateType);
+ auto extents = atspiObject->textExtents(offset, offset + 1, coordinateType);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(iiii)", extents.x(), extents.y(), extents.width(), extents.height()));
+ } else if (!g_strcmp0(methodName, "GetRangeExtents")) {
+ int start, end;
+ uint32_t coordinateType;
+ g_variant_get(parameters, "(iiu)", &start, &end, &coordinateType);
+ auto extents = atspiObject->textExtents(start, end, coordinateType);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(iiii)", extents.x(), extents.y(), extents.width(), extents.height()));
+ } else if (!g_strcmp0(methodName, "GetOffsetAtPoint")) {
+ int x, y;
+ uint32_t coordinateType;
+ g_variant_get(parameters, "(iiu)", &x, &y, &coordinateType);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", atspiObject->offsetAtPoint(IntPoint(x, y), coordinateType)));
+ } else if (!g_strcmp0(methodName, "GetNSelections")) {
+ int start, end;
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", atspiObject->selectionBounds(start, end) && start != end ? 1 : 0));
+ } else if (!g_strcmp0(methodName, "GetSelection")) {
+ int selectionNumber;
+ g_variant_get(parameters, "(i)", &selectionNumber);
+ if (selectionNumber)
+ g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Not a valid selection: %d", selectionNumber);
+ else {
+ int start = 0, end = 0;
+ if (atspiObject->selectionBounds(start, end) && start == end)
+ start = end = 0;
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(ii)", start, end));
+ }
+ } else if (!g_strcmp0(methodName, "AddSelection")) {
+ int start, end;
+ g_variant_get(parameters, "(ii)", &start, &end);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", atspiObject->selectRange(start, end)));
+ } else if (!g_strcmp0(methodName, "SetSelection")) {
+ int start, end, selectionNumber;
+ g_variant_get(parameters, "(iii)", &selectionNumber, &start, &end);
+ if (selectionNumber)
+ g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Not a valid selection: %d", selectionNumber);
+ else
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", atspiObject->selectRange(start, end)));
+ } else if (!g_strcmp0(methodName, "RemoveSelection")) {
+ int selectionNumber;
+ int caretOffset = -1;
+ if (!selectionNumber) {
+ int start, end;
+ if (atspiObject->selectionBounds(start, end))
+ caretOffset = end;
+ }
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", caretOffset != -1 ? atspiObject->selectRange(caretOffset, caretOffset) : FALSE));
+ } else if (!g_strcmp0(methodName, "ScrollSubstringTo")) {
+ int start, end;
+ uint32_t scrollType;
+ g_variant_get(parameters, "(iiu)", &start, &end, &scrollType);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", atspiObject->scrollToMakeVisible(start, end, scrollType)));
+ } else if (!g_strcmp0(methodName, "ScrollSubstringToPoint")) {
+ int start, end, x, y;
+ uint32_t coordinateType;
+ g_variant_get(parameters, "(iiuii)", &start, &end, &coordinateType, &x, &y);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", atspiObject->scrollToPoint(start, end, coordinateType, x, y)));
+ } else if (!g_strcmp0(methodName, "GetBoundedRanges") || !g_strcmp0(methodName, "ScrollSubstringToPoint"))
+ 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, "CharacterCount"))
+ return g_variant_new_int32(g_utf8_strlen(atspiObject->text().utf8().data(), -1));
+ if (!g_strcmp0(propertyName, "CaretOffset")) {
+ int start = 0, end = 0;
+ return g_variant_new_int32(atspiObject->selectionBounds(start, end) ? end : -1);
+ }
+
+ g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unknown property '%s'", propertyName);
+ return nullptr;
+ },
+ // set_property,
+ nullptr,
+ // padding
+ nullptr
+};
+
+static Vector<unsigned, 128> offsetMapping(const String& text)
+{
+ if (text.is8Bit())
+ return { };
+
+ Vector<unsigned, 128> offsets;
+ SurrogatePairAwareTextIterator iterator(text.characters16(), 0, text.length(), text.length());
+ UChar32 character;
+ unsigned clusterLength = 0;
+ unsigned i;
+ for (i = 0; iterator.consume(character, clusterLength); iterator.advance(clusterLength), ++i) {
+ for (unsigned j = 0; j < clusterLength; ++j)
+ offsets.append(i);
+ }
+ offsets.append(i++);
+ return offsets;
+}
+
+static inline unsigned UTF16OffsetToUTF8(const Vector<unsigned, 128>& mapping, unsigned offset)
+{
+ return mapping.isEmpty() ? offset : mapping[offset];
+}
+
+static inline unsigned UTF8OffsetToUTF16(const Vector<unsigned, 128>& mapping, unsigned offset)
+{
+ if (mapping.isEmpty())
+ return offset;
+
+ for (unsigned i = offset; i < mapping.size(); ++i) {
+ if (mapping[i] == offset)
+ return i;
+ }
+ return mapping.size();
+}
+
+String AccessibilityObjectAtspi::text() const
+{
+ AXCoreObject* axObject = isMainThread() ? m_coreObject : m_axObject;
+ if (!axObject)
+ return { };
+
+#if ENABLE(INPUT_TYPE_COLOR)
+ if (axObject->roleValue() == AccessibilityRole::ColorWell) {
+ auto color = convertColor<SRGBA<float>>(axObject->colorValue());
+ GUniquePtr<char> colorString(g_strdup_printf("rgb %7.5f %7.5f %7.5f 1", color.red, color.green, color.blue));
+ return String::fromUTF8(colorString.get());
+ }
+#endif
+
+ if (axObject->isTextControl())
+ return axObject->doAXStringForRange({ 0, String::MaxLength });
+
+ auto value = axObject->stringValue();
+ 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();
+
+ if (!m_coreObject)
+ return { };
+ return m_coreObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)).isolatedCopy();
+ });
+}
+
+unsigned AccessibilityObject::getLengthForTextRange() const
+{
+ RELEASE_ASSERT(isMainThread());
+ // FIXME: this should probably be in sync with AccessibilityObjectAtspi::text().
+ unsigned textLength = text().length();
+ if (textLength)
+ return textLength;
+
+ Node* node = this->node();
+ RenderObject* renderer = node ? node->renderer() : nullptr;
+ if (is<RenderText>(renderer))
+ textLength = downcast<RenderText>(*renderer).text().length();
+
+ if (!textLength && allowsTextRanges())
+ textLength = textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)).length();
+
+ return textLength;
+}
+
+bool AccessibilityObject::allowsTextRanges() const
+{
+ return true;
+}
+
+CString AccessibilityObjectAtspi::text(int startOffset, int endOffset) const
+{
+ RELEASE_ASSERT(!isMainThread());
+ auto utf16Text = text();
+ auto utf8Text = utf16Text.utf8();
+ if (utf8Text.isNull())
+ return { };
+
+ auto length = static_cast<int>(g_utf8_strlen(utf8Text.data(), -1));
+ if (endOffset == -1)
+ endOffset = length;
+
+ if (startOffset < 0 || endOffset < 0)
+ return { };
+
+ if (endOffset <= startOffset)
+ return { };
+
+ if (!startOffset && endOffset == length)
+ return utf8Text;
+
+ GUniquePtr<char> substring(g_utf8_substring(utf8Text.data(), startOffset, endOffset));
+ return substring.get();
+}
+
+void AccessibilityObjectAtspi::textInserted(const String& insertedText, const VisiblePosition& position)
+{
+ RELEASE_ASSERT(isMainThread());
+ if (!m_isRegistered.load())
+ return;
+
+ if (!m_interfaces.contains(Interface::Text))
+ return;
+
+ auto utf16Text = text();
+ auto utf8Text = utf16Text.utf8();
+ auto utf16Offset = m_coreObject->indexForVisiblePosition(position);
+ auto mapping = offsetMapping(utf16Text);
+ auto offset = UTF16OffsetToUTF8(mapping, utf16Offset);
+ auto utf8InsertedText = insertedText.utf8();
+ auto insertedTextLength = g_utf8_strlen(utf8InsertedText.data(), -1);
+ root()->atspi().textChanged(*this, "insert", WTFMove(utf8InsertedText), offset - insertedTextLength, insertedTextLength);
+}
+
+void AccessibilityObjectAtspi::textDeleted(const String& deletedText, const VisiblePosition& position)
+{
+ RELEASE_ASSERT(isMainThread());
+ if (!m_isRegistered.load())
+ return;
+
+ if (!m_interfaces.contains(Interface::Text))
+ return;
+
+ auto utf16Text = text();
+ auto utf8Text = utf16Text.utf8();
+ auto utf16Offset = m_coreObject->indexForVisiblePosition(position);
+ auto mapping = offsetMapping(utf16Text);
+ auto offset = UTF16OffsetToUTF8(mapping, utf16Offset);
+ auto utf8DeletedText = deletedText.utf8();
+ auto deletedTextLength = g_utf8_strlen(utf8DeletedText.data(), -1);
+ root()->atspi().textChanged(*this, "delete", WTFMove(utf8DeletedText), offset, deletedTextLength);
+}
+
+IntPoint AccessibilityObjectAtspi::boundaryOffset(unsigned utf16Offset, TextGranularity granularity) const
+{
+ return Accessibility::retrieveValueFromMainThread<IntPoint>([this, utf16Offset, granularity]() -> IntPoint {
+ if (m_coreObject)
+ m_coreObject->updateBackingStore();
+
+ if (!m_coreObject)
+ return { };
+
+ VisiblePosition offsetPosition = m_coreObject->visiblePositionForIndex(utf16Offset);
+ VisiblePosition startPosition, endPostion;
+ switch (granularity) {
+ case TextGranularity::Character:
+ RELEASE_ASSERT_NOT_REACHED();
+ case TextGranularity::WordStart: {
+ startPosition = isStartOfWord(offsetPosition) && deprecatedIsEditingWhitespace(offsetPosition.characterBefore()) ? offsetPosition : startOfWord(offsetPosition, LeftWordIfOnBoundary);
+ endPostion = nextWordPosition(startPosition);
+ auto positionAfterSpacingAndFollowingWord = nextWordPosition(endPostion);
+ if (positionAfterSpacingAndFollowingWord != endPostion) {
+ auto previousPosition = previousWordPosition(positionAfterSpacingAndFollowingWord);
+ if (previousPosition == startPosition)
+ endPostion = positionAfterSpacingAndFollowingWord;
+ else
+ endPostion = previousPosition;
+ }
+ break;
+ }
+ case TextGranularity::WordEnd: {
+ startPosition = previousWordPosition(offsetPosition);
+ auto positionBeforeSpacingAndPreviousWord = previousWordPosition(startPosition);
+ if (positionBeforeSpacingAndPreviousWord != startPosition)
+ startPosition = nextWordPosition(positionBeforeSpacingAndPreviousWord);
+ endPostion = endOfWord(offsetPosition);
+ break;
+ }
+ case TextGranularity::SentenceStart: {
+ startPosition = startOfSentence(offsetPosition);
+ endPostion = endOfSentence(startPosition);
+ if (offsetPosition == endPostion) {
+ startPosition = nextSentencePosition(startPosition);
+ endPostion = endOfSentence(startPosition);
+ }
+ break;
+ }
+ case TextGranularity::SentenceEnd:
+ startPosition = previousSentencePosition(offsetPosition);
+ endPostion = endOfSentence(offsetPosition);
+ break;
+ case TextGranularity::LineStart:
+ startPosition = logicalStartOfLine(offsetPosition);
+ endPostion = nextLinePosition(offsetPosition, 0);
+ break;
+ case TextGranularity::LineEnd:
+ startPosition = logicalStartOfLine(offsetPosition);
+ endPostion = logicalEndOfLine(offsetPosition);
+ break;
+ case TextGranularity::Paragraph:
+ startPosition = startOfParagraph(offsetPosition);
+ endPostion = endOfParagraph(offsetPosition);
+ break;
+ }
+ return { m_coreObject->indexForVisiblePosition(startPosition), m_coreObject->indexForVisiblePosition(endPostion) };
+ });
+}
+
+CString AccessibilityObjectAtspi::textAtOffset(int offset, TextGranularity granularity, int& startOffset, int& endOffset) const
+{
+ RELEASE_ASSERT(!isMainThread());
+ auto utf16Text = text();
+ auto utf8Text = utf16Text.utf8();
+ if (utf8Text.isNull())
+ return { };
+
+ auto length = static_cast<int>(g_utf8_strlen(utf8Text.data(), -1));
+ if (offset < 0 || offset > length)
+ return { };
+
+ if (granularity == TextGranularity::Character) {
+ startOffset = offset;
+ endOffset = std::min(offset + 1, length);
+ } else {
+ auto mapping = offsetMapping(utf16Text);
+ auto utf16Offset = UTF8OffsetToUTF16(mapping, offset);
+ auto boundaryOffset = this->boundaryOffset(utf16Offset, granularity);
+ startOffset = UTF16OffsetToUTF8(mapping, std::max<int>(boundaryOffset.x(), 0));
+ endOffset = UTF16OffsetToUTF8(mapping, std::min<int>(boundaryOffset.y(), utf16Text.length()));
+ }
+
+ GUniquePtr<char> substring(g_utf8_substring(utf8Text.data(), startOffset, endOffset));
+ return substring.get();
+}
+
+int AccessibilityObjectAtspi::characterAtOffset(int offset) const
+{
+ RELEASE_ASSERT(!isMainThread());
+ auto utf8Text = text().utf8();
+ if (utf8Text.isNull())
+ return 0;
+
+ auto length = static_cast<int>(g_utf8_strlen(utf8Text.data(), -1));
+ if (offset < 0 || offset >= length)
+ return 0;
+
+ return g_utf8_get_char(g_utf8_offset_to_pointer(utf8Text.data(), offset));
+}
+
+IntRect AccessibilityObjectAtspi::boundsForRange(unsigned utf16Offset, unsigned length, uint32_t coordinateType) const
+{
+ return Accessibility::retrieveValueFromMainThread<IntRect>([this, utf16Offset, length, coordinateType]() -> IntRect {
+ if (m_coreObject)
+ m_coreObject->updateBackingStore();
+
+ if (!m_coreObject)
+ return { };
+
+ auto extents = m_coreObject->doAXBoundsForRange(PlainTextRange(utf16Offset, length));
+
+ auto* frameView = m_coreObject->documentFrameView();
+ if (!frameView)
+ return extents;
+
+ switch (coordinateType) {
+ case Atspi::CoordinateType::ScreenCoordinates:
+ return frameView->contentsToScreen(extents);
+ case Atspi::CoordinateType::WindowCoordinates:
+ return frameView->contentsToWindow(extents);
+ case Atspi::CoordinateType::ParentCoordinates:
+ return extents;
+ }
+
+ RELEASE_ASSERT_NOT_REACHED();
+ });
+}
+
+IntRect AccessibilityObjectAtspi::textExtents(int startOffset, int endOffset, uint32_t coordinateType) const
+{
+ RELEASE_ASSERT(!isMainThread());
+ auto utf16Text = text();
+ auto utf8Text = utf16Text.utf8();
+ if (utf8Text.isNull())
+ return { };
+
+ auto length = static_cast<int>(g_utf8_strlen(utf8Text.data(), -1));
+ startOffset = std::clamp(startOffset, 0, length);
+ if (endOffset == -1)
+ endOffset = length;
+ else
+ endOffset = std::clamp(endOffset, 0, length);
+ if (endOffset <= startOffset)
+ return { };
+
+ auto mapping = offsetMapping(utf16Text);
+ auto utf16StartOffset = UTF8OffsetToUTF16(mapping, startOffset);
+ auto utf16EndOffset = UTF8OffsetToUTF16(mapping, endOffset);
+ return boundsForRange(utf16StartOffset, utf16EndOffset - utf16StartOffset, coordinateType);
+}
+
+int AccessibilityObjectAtspi::offsetAtPoint(const IntPoint& point, uint32_t coordinateType) const
+{
+ RELEASE_ASSERT(!isMainThread());
+ auto utf16Text = text();
+ auto utf8Text = utf16Text.utf8();
+ if (utf8Text.isNull())
+ return -1;
+
+ auto utf16Offset = Accessibility::retrieveValueFromMainThread<int>([this, &point, coordinateType]() -> int {
+ if (m_coreObject)
+ m_coreObject->updateBackingStore();
+
+ if (!m_coreObject)
+ return -1;
+
+ auto convertedPoint = point;
+ if (auto* frameView = m_coreObject->documentFrameView()) {
+ switch (coordinateType) {
+ case Atspi::CoordinateType::ScreenCoordinates:
+ convertedPoint = frameView->screenToContents(point);
+ break;
+ case Atspi::CoordinateType::WindowCoordinates:
+ convertedPoint = frameView->windowToContents(point);
+ break;
+ case Atspi::CoordinateType::ParentCoordinates:
+ break;
+ }
+ }
+
+ auto position = m_coreObject->visiblePositionForPoint(convertedPoint);
+ if (position.isNull())
+ return -1;
+
+ return m_coreObject->indexForVisiblePosition(position);
+ });
+
+ if (utf16Offset == -1)
+ return -1;
+
+ return UTF16OffsetToUTF8(offsetMapping(utf16Text), utf16Offset);
+}
+
+IntPoint AccessibilityObjectAtspi::boundsForSelection(const VisibleSelection& selection) const
+{
+ RELEASE_ASSERT(isMainThread());
+ if (selection.isNone())
+ return { -1, -1 };
+
+ Node* node = nullptr;
+ if (!m_coreObject->isNativeTextControl())
+ node = m_coreObject->node();
+ else {
+ auto positionInTextControlInnerElement = m_coreObject->visiblePositionForIndex(0);
+ if (auto* innerMostNode = positionInTextControlInnerElement.deepEquivalent().anchorNode())
+ node = innerMostNode->parentNode();
+ }
+ if (!node)
+ return { -1, -1 };
+
+ // We need to limit our search to positions that fall inside the domain of the current object.
+ auto firstValidPosition = firstPositionInOrBeforeNode(node->firstDescendant());
+ auto lastValidPosition = lastPositionInOrAfterNode(node->lastDescendant());
+
+ if (!intersects(makeVisiblePositionRange(makeSimpleRange(firstValidPosition, lastValidPosition)), VisiblePositionRange(selection)))
+ return { -1, -1 };
+
+ // Find the proper range for the selection that falls inside the object.
+ auto nodeRangeStart = std::max(selection.start(), firstValidPosition);
+ auto nodeRangeEnd = std::min(selection.end(), lastValidPosition);
+
+ // Calculate position of the selected range inside the object.
+ auto parentFirstPosition = firstPositionInOrBeforeNode(node);
+ auto rangeInParent = *makeSimpleRange(parentFirstPosition, nodeRangeStart);
+
+ // Set values for start offsets and calculate initial range length.
+ int startOffset = characterCount(rangeInParent, TextIteratorBehavior::EmitsCharactersBetweenAllVisiblePositions);
+ auto nodeRange = *makeSimpleRange(nodeRangeStart, nodeRangeEnd);
+ int rangeLength = characterCount(nodeRange, TextIteratorBehavior::EmitsCharactersBetweenAllVisiblePositions);
+ return { startOffset, startOffset + rangeLength };
+}
+
+IntPoint AccessibilityObjectAtspi::selectedRange() const
+{
+ return Accessibility::retrieveValueFromMainThread<IntPoint>([this]() -> IntPoint {
+ if (m_coreObject)
+ m_coreObject->updateBackingStore();
+
+ if (!m_coreObject)
+ return { -1, -1 };
+
+ return boundsForSelection(m_coreObject->selection());
+ });
+}
+
+bool AccessibilityObjectAtspi::selectionBounds(int& startOffset, int& endOffset) const
+{
+ RELEASE_ASSERT(!isMainThread());
+ auto utf16Text = text();
+ auto utf8Text = utf16Text.utf8();
+ if (utf8Text.isNull())
+ return false;
+
+ auto bounds = selectedRange();
+ if (bounds.x() < 0)
+ return false;
+
+ auto mapping = offsetMapping(utf16Text);
+ startOffset = UTF16OffsetToUTF8(mapping, bounds.x());
+ endOffset = UTF16OffsetToUTF8(mapping, bounds.y());
+
+ auto length = static_cast<int>(g_utf8_strlen(utf8Text.data(), -1));
+ endOffset = std::clamp(endOffset, 0, length);
+ if (endOffset < startOffset) {
+ startOffset = endOffset = 0;
+ return false;
+ }
+
+ return true;
+}
+
+void AccessibilityObjectAtspi::setSelectedRange(unsigned utf16Offset, unsigned length)
+{
+ Accessibility::performFunctionOnMainThread([this, utf16Offset, length] {
+ if (m_coreObject)
+ m_coreObject->updateBackingStore();
+
+ if (!m_coreObject)
+ return;
+
+ auto range = m_coreObject->visiblePositionRangeForRange(PlainTextRange(utf16Offset, length));
+ m_coreObject->setSelectedVisiblePositionRange(range);
+ });
+}
+
+bool AccessibilityObjectAtspi::selectRange(int startOffset, int endOffset)
+{
+ RELEASE_ASSERT(!isMainThread());
+ auto utf16Text = text();
+ auto utf8Text = utf16Text.utf8();
+ if (utf8Text.isNull())
+ return false;
+
+ auto length = static_cast<int>(g_utf8_strlen(utf8Text.data(), -1));
+ startOffset = std::clamp(startOffset, 0, length);
+ if (endOffset == -1)
+ endOffset = length;
+ else
+ endOffset = std::clamp(endOffset, 0, length);
+
+ auto mapping = offsetMapping(utf16Text);
+ auto utf16StartOffset = UTF8OffsetToUTF16(mapping, startOffset);
+ auto utf16EndOffset = startOffset == endOffset ? utf16StartOffset : UTF8OffsetToUTF16(mapping, endOffset);
+ setSelectedRange(utf16StartOffset, utf16EndOffset - utf16StartOffset);
+
+ return true;
+}
+
+void AccessibilityObjectAtspi::selectionChanged(const VisibleSelection& selection)
+{
+ RELEASE_ASSERT(isMainThread());
+ if (!m_isRegistered.load())
+ return;
+
+ if (!m_interfaces.contains(Interface::Text))
+ return;
+
+ if (selection.isNone())
+ return;
+
+ auto utf16Text = text();
+ auto utf8Text = utf16Text.utf8();
+ if (utf8Text.isNull())
+ return;
+
+ auto bounds = boundsForSelection(selection);
+ if (bounds.y() < 0)
+ return;
+
+ auto length = g_utf8_strlen(utf8Text.data(), -1);
+ auto mapping = offsetMapping(utf16Text);
+ auto caretOffset = UTF16OffsetToUTF8(mapping, bounds.y());
+ if (caretOffset <= length)
+ root()->atspi().textCaretMoved(*this, caretOffset);
+
+ if (selection.isRange())
+ root()->atspi().textSelectionChanged(*this);
+}
+
+AccessibilityObjectAtspi::TextAttributes AccessibilityObjectAtspi::textAttributes(std::optional<unsigned> utf16Offset, bool includeDefault) const
+{
+ return Accessibility::retrieveValueFromMainThread<TextAttributes>([this, utf16Offset, includeDefault]() -> TextAttributes {
+ if (m_coreObject)
+ m_coreObject->updateBackingStore();
+
+ if (!m_coreObject || !m_coreObject->renderer() || !m_coreObject->node())
+ return { };
+
+ auto accessibilityTextAttributes = [this](AXCoreObject* axObject, const HashMap<String, String>& defaultAttributes) -> HashMap<String, String> {
+ HashMap<String, String> attributes;
+ auto& style = axObject->renderer()->style();
+
+ auto addAttributeIfNeeded = [&](const String& name, const String& value) {
+ if (defaultAttributes.isEmpty() || defaultAttributes.get(name) != value)
+ attributes.add(name, value);
+ };
+
+ auto bgColor = style.visitedDependentColor(CSSPropertyBackgroundColor);
+ if (bgColor.isValid() && bgColor.isVisible()) {
+ auto [r, g, b, a] = bgColor.toSRGBALossy<uint8_t>();
+ addAttributeIfNeeded("bg-color"_s, makeString(r, ',', g, ',', b));
+ }
+
+ auto fgColor = style.visitedDependentColor(CSSPropertyColor);
+ if (fgColor.isValid() && fgColor.isVisible()) {
+ auto [r, g, b, a] = fgColor.toSRGBALossy<uint8_t>();
+ addAttributeIfNeeded("fg-color"_s, makeString(r, ',', g, ',', b));
+ }
+
+ addAttributeIfNeeded("family-name"_s, style.fontCascade().firstFamily());
+ addAttributeIfNeeded("size"_s, makeString(std::round(style.computedFontPixelSize() * 72 / WebCore::screenDPI()), "pt"));
+ addAttributeIfNeeded("weight"_s, makeString(static_cast<float>(style.fontCascade().weight())));
+ addAttributeIfNeeded("style"_s, style.fontCascade().italic() ? "italic" : "normal");
+ addAttributeIfNeeded("strikethrough"_s, style.textDecoration() & TextDecoration::LineThrough ? "true" : "false");
+ addAttributeIfNeeded("underline"_s, style.textDecoration() & TextDecoration::Underline ? "single" : "none");
+ addAttributeIfNeeded("invisible"_s, style.visibility() == Visibility::Hidden ? "true" : "false");
+ addAttributeIfNeeded("editable"_s, m_coreObject->canSetValueAttribute() ? "true" : "false");
+ addAttributeIfNeeded("direction"_s, style.direction() == TextDirection::LTR ? "ltr" : "rtl");
+
+ if (!style.textIndent().isUndefined())
+ addAttributeIfNeeded("indent"_s, makeString(valueForLength(style.textIndent(), m_coreObject->size().width()).toInt()));
+
+ switch (style.textAlign()) {
+ case TextAlignMode::Start:
+ case TextAlignMode::End:
+ break;
+ case TextAlignMode::Left:
+ case TextAlignMode::WebKitLeft:
+ addAttributeIfNeeded("justification"_s, "left");
+ break;
+ case TextAlignMode::Right:
+ case TextAlignMode::WebKitRight:
+ addAttributeIfNeeded("justification"_s, "right");
+ break;
+ case TextAlignMode::Center:
+ case TextAlignMode::WebKitCenter:
+ addAttributeIfNeeded("justification"_s, "center");
+ break;
+ case TextAlignMode::Justify:
+ addAttributeIfNeeded("justification"_s, "fill");
+ break;
+ }
+
+ String invalidStatus = m_coreObject->invalidStatus();
+ if (invalidStatus != "false")
+ addAttributeIfNeeded("invalid"_s, invalidStatus);
+
+ String language = m_coreObject->language();
+ if (!language.isEmpty())
+ addAttributeIfNeeded("language"_s, language);
+
+ return attributes;
+ };
+
+ auto defaultAttributes = accessibilityTextAttributes(m_coreObject, { });
+ if (!utf16Offset)
+ return { defaultAttributes, -1, -1 };
+
+ VisiblePosition offsetPosition = m_coreObject->visiblePositionForIndex(*utf16Offset);
+ auto* childNode = offsetPosition.deepEquivalent().deprecatedNode();
+ if (!childNode)
+ return { defaultAttributes, -1, -1 };
+
+ auto* childRenderer = childNode->renderer();
+ if (!childRenderer)
+ return { defaultAttributes, -1, -1 };
+
+ auto* childAxObject = childRenderer->document().axObjectCache()->get(childRenderer);
+ if (!childAxObject || childAxObject == m_coreObject)
+ return { defaultAttributes, -1, -1 };
+
+ auto attributes = accessibilityTextAttributes(childAxObject, defaultAttributes);
+ auto firstValidPosition = firstPositionInOrBeforeNode(m_coreObject->node()->firstDescendant());
+ auto lastValidPosition = lastPositionInOrAfterNode(m_coreObject->node()->lastDescendant());
+
+ auto* startRenderer = childRenderer;
+ auto startPosition = firstPositionInOrBeforeNode(startRenderer->node());
+ for (RenderObject* r = childRenderer->previousInPreOrder(); r && startPosition > firstValidPosition; r = r->previousInPreOrder()) {
+ if (r->firstChildSlow())
+ continue;
+
+ auto childAttributes = accessibilityTextAttributes(r->document().axObjectCache()->get(r), defaultAttributes);
+ if (childAttributes != attributes)
+ break;
+
+ startRenderer = r;
+ startPosition = firstPositionInOrBeforeNode(startRenderer->node());
+ }
+
+ auto* endRenderer = childRenderer;
+ auto endPosition = lastPositionInOrAfterNode(endRenderer->node());
+ for (RenderObject* r = childRenderer->nextInPreOrder(); r && endPosition < lastValidPosition; r = r->nextInPreOrder()) {
+ if (r->firstChildSlow())
+ continue;
+
+ auto childAttributes = accessibilityTextAttributes(r->document().axObjectCache()->get(r), defaultAttributes);
+ if (childAttributes != attributes)
+ break;
+
+ endRenderer = r;
+ endPosition = lastPositionInOrAfterNode(endRenderer->node());
+ }
+
+ if (!includeDefault)
+ return { attributes, m_coreObject->indexForVisiblePosition(startPosition), m_coreObject->indexForVisiblePosition(endPosition) };
+
+ for (const auto& it : attributes)
+ defaultAttributes.set(it.key, it.value);
+
+ return { defaultAttributes, m_coreObject->indexForVisiblePosition(startPosition), m_coreObject->indexForVisiblePosition(endPosition) };
+ });
+}
+
+AccessibilityObjectAtspi::TextAttributes AccessibilityObjectAtspi::textAttributesWithUTF8Offset(std::optional<int> offset, bool includeDefault) const
+{
+ RELEASE_ASSERT(!isMainThread());
+ auto utf16Text = text();
+ auto utf8Text = utf16Text.utf8();
+ if (utf8Text.isNull())
+ return { };
+
+ auto mapping = offsetMapping(utf16Text);
+ std::optional<unsigned> utf16Offset;
+ if (offset) {
+ auto length = static_cast<int>(g_utf8_strlen(utf8Text.data(), -1));
+ if (*offset < 0 || *offset >= length)
+ return { };
+
+ utf16Offset = UTF8OffsetToUTF16(mapping, *offset);
+ }
+
+ auto attributes = textAttributes(utf16Offset, includeDefault);
+ if (attributes.startOffset != -1)
+ attributes.startOffset = UTF16OffsetToUTF8(mapping, attributes.startOffset);
+ if (attributes.endOffset != -1)
+ attributes.endOffset = UTF16OffsetToUTF8(mapping, attributes.endOffset);
+
+ return attributes;
+}
+
+void AccessibilityObjectAtspi::textAttributesChanged()
+{
+ RELEASE_ASSERT(isMainThread());
+ if (!m_isRegistered.load())
+ return;
+
+ if (!m_interfaces.contains(Interface::Text))
+ return;
+
+ root()->atspi().textAttributesChanged(*this);
+}
+
+bool AccessibilityObjectAtspi::scrollToMakeVisible(int startOffset, int endOffset, uint32_t scrollType) const
+{
+ RELEASE_ASSERT(!isMainThread());
+ auto utf16Text = text();
+ auto utf8Text = utf16Text.utf8();
+ if (utf8Text.isNull())
+ return false;
+
+ auto length = static_cast<int>(g_utf8_strlen(utf8Text.data(), -1));
+ if (startOffset < 0 || startOffset > length)
+ return false;
+ if (endOffset < 0 || endOffset > length)
+ return false;
+ if (endOffset < startOffset)
+ std::swap(startOffset, endOffset);
+
+ auto mapping = offsetMapping(utf16Text);
+ auto utf16StartOffset = UTF8OffsetToUTF16(mapping, startOffset);
+ auto utf16EndOffset = UTF8OffsetToUTF16(mapping, endOffset);
+ Accessibility::performFunctionOnMainThread([this, utf16StartOffset, utf16EndOffset, scrollType] {
+ if (m_coreObject)
+ m_coreObject->updateBackingStore();
+
+ if (!m_coreObject || !m_coreObject->renderer())
+ return;
+
+ IntRect rect = m_coreObject->doAXBoundsForRange(PlainTextRange(utf16StartOffset, utf16EndOffset - utf16StartOffset));
+
+ if (m_coreObject->isScrollView()) {
+ if (auto* parent = m_coreObject->parentObject())
+ parent->scrollToMakeVisible();
+ }
+
+ ScrollAlignment alignX;
+ ScrollAlignment alignY;
+ switch (scrollType) {
+ case Atspi::ScrollType::TopLeft:
+ alignX = ScrollAlignment::alignLeftAlways;
+ alignY = ScrollAlignment::alignTopAlways;
+ break;
+ case Atspi::ScrollType::BottomRight:
+ alignX = ScrollAlignment::alignRightAlways;
+ alignY = ScrollAlignment::alignBottomAlways;
+ break;
+ case Atspi::ScrollType::TopEdge:
+ case Atspi::ScrollType::BottomEdge:
+ // Align to a particular edge is not supported, it's always the closest edge.
+ alignX = ScrollAlignment::alignCenterIfNeeded;
+ alignY = ScrollAlignment::alignToEdgeIfNeeded;
+ break;
+ case Atspi::ScrollType::LeftEdge:
+ case Atspi::ScrollType::RightEdge:
+ // Align to a particular edge is not supported, it's always the closest edge.
+ alignX = ScrollAlignment::alignToEdgeIfNeeded;
+ alignY = ScrollAlignment::alignCenterIfNeeded;
+ break;
+ case Atspi::ScrollType::Anywhere:
+ alignX = ScrollAlignment::alignCenterIfNeeded;
+ alignY = ScrollAlignment::alignCenterIfNeeded;
+ break;
+ }
+
+ m_coreObject->renderer()->scrollRectToVisible(rect, false, { SelectionRevealMode::Reveal, alignX, alignY, ShouldAllowCrossOriginScrolling::Yes });
+ });
+
+ return true;
+}
+
+bool AccessibilityObjectAtspi::scrollToPoint(int startOffset, int endOffset, uint32_t coordinateType, int x, int y) const
+{
+ RELEASE_ASSERT(!isMainThread());
+ auto utf16Text = text();
+ auto utf8Text = utf16Text.utf8();
+ if (utf8Text.isNull())
+ return false;
+
+ auto length = static_cast<int>(g_utf8_strlen(utf8Text.data(), -1));
+ if (startOffset < 0 || startOffset > length)
+ return false;
+ if (endOffset < 0 || endOffset > length)
+ return false;
+ if (endOffset < startOffset)
+ std::swap(startOffset, endOffset);
+
+ auto mapping = offsetMapping(utf16Text);
+ auto utf16StartOffset = UTF8OffsetToUTF16(mapping, startOffset);
+ auto utf16EndOffset = UTF8OffsetToUTF16(mapping, endOffset);
+ Accessibility::performFunctionOnMainThread([this, utf16StartOffset, utf16EndOffset, coordinateType, x, y] {
+ if (m_coreObject)
+ m_coreObject->updateBackingStore();
+
+ if (!m_coreObject)
+ return;
+
+ IntPoint point(x, y);
+ if (coordinateType == Atspi::CoordinateType::ScreenCoordinates) {
+ if (auto* frameView = m_coreObject->documentFrameView())
+ point = frameView->contentsToWindow(frameView->screenToContents(point));
+ }
+
+ IntRect rect = m_coreObject->doAXBoundsForRange(PlainTextRange(utf16StartOffset, utf16EndOffset - utf16StartOffset));
+ point.move(-rect.x(), -rect.y());
+ m_coreObject->scrollToGlobalPoint(point);
+ });
+
+ return true;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(ACCESSIBILITY) && USE(ATSPI)
</ins></span></pre></div>
<a id="trunkSourceWebCoreeditingatspiFrameSelectionAtspicpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/editing/atspi/FrameSelectionAtspi.cpp (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/editing/atspi/FrameSelectionAtspi.cpp 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/editing/atspi/FrameSelectionAtspi.cpp 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -23,13 +23,20 @@
</span><span class="cx"> #if ENABLE(ACCESSIBILITY) && USE(ATSPI)
</span><span class="cx">
</span><span class="cx"> #include "AXObjectCache.h"
</span><ins>+#include "DocumentInlines.h"
</ins><span class="cx">
</span><span class="cx"> namespace WebCore {
</span><span class="cx">
</span><del>-void FrameSelection::notifyAccessibilityForSelectionChange(const AXTextStateChangeIntent&)
</del><ins>+void FrameSelection::notifyAccessibilityForSelectionChange(const AXTextStateChangeIntent& intent)
</ins><span class="cx"> {
</span><span class="cx"> if (!AXObjectCache::accessibilityEnabled())
</span><span class="cx"> return;
</span><ins>+
+ if (!m_selection.start().isNotNull() || !m_selection.end().isNotNull())
+ return;
+
+ if (AXObjectCache* cache = m_document->existingAXObjectCache())
+ cache->postTextStateChangeNotification(m_selection.start(), intent, m_selection);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> } // namespace WebCore
</span></span></pre></div>
<a id="trunkSourceWebCorehtmlHTMLTextFormControlElementcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/html/HTMLTextFormControlElement.cpp (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/html/HTMLTextFormControlElement.cpp 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Source/WebCore/html/HTMLTextFormControlElement.cpp 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -603,7 +603,7 @@
</span><span class="cx"> innerText->appendChild(HTMLBRElement::create(document()));
</span><span class="cx"> }
</span><span class="cx">
</span><del>-#if ENABLE(ACCESSIBILITY) && PLATFORM(COCOA)
</del><ins>+#if ENABLE(ACCESSIBILITY) && (PLATFORM(COCOA) || USE(ATSPI))
</ins><span class="cx"> if (textIsChanged && renderer()) {
</span><span class="cx"> if (AXObjectCache* cache = document().existingAXObjectCache())
</span><span class="cx"> cache->deferTextReplacementNotificationForTextControl(*this, previousValue);
</span></span></pre></div>
<a id="trunkToolsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Tools/ChangeLog (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/ChangeLog 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Tools/ChangeLog 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -1,3 +1,26 @@
</span><ins>+2021-10-22 Carlos Garcia Campos <cgarcia@igalia.com>
+
+ [GTK][a11y] Add implementation of text interface when building with ATSPI
+ https://bugs.webkit.org/show_bug.cgi?id=230258
+
+ Reviewed by Adrian Perez de Castro.
+
+ Add unit tests for the text interface.
+
+ * TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp:
+ (AtspiTextRangeDeleter::operator() const):
+ (AccessibilityTest::startEventMonitor):
+ (AccessibilityTest::stopEventMonitor):
+ (AccessibilityTest::findEvent):
+ (testTextBasic):
+ (testTextSurrogatePair):
+ (testTextIterator):
+ (testTextExtents):
+ (testTextSelections):
+ (testTextAttributes):
+ (testTextStateChanged):
+ (beforeAll):
+
</ins><span class="cx"> 2021-10-22 Kimmo Kinnunen <kkinnunen@apple.com>
</span><span class="cx">
</span><span class="cx"> WebGL low-power and high-performance contexts should use different ANGLE Metal EGLDisplays
</span></span></pre></div>
<a id="trunkToolsTestWebKitAPITestsWebKitGtkTestWebKitAccessibilitycpp"></a>
<div class="modfile"><h4>Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp (284674 => 284675)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp 2021-10-22 08:44:57 UTC (rev 284674)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitGtk/TestWebKitAccessibility.cpp 2021-10-22 08:51:03 UTC (rev 284675)
</span><span class="lines">@@ -34,7 +34,15 @@
</span><span class="cx"> }
</span><span class="cx"> };
</span><span class="cx">
</span><ins>+struct AtspiTextRangeDeleter {
+ void operator()(AtspiTextRange* range) const
+ {
+ g_boxed_free(ATSPI_TYPE_TEXT_RANGE, range);
+ }
+};
+
</ins><span class="cx"> using UniqueAtspiEvent = std::unique_ptr<AtspiEvent, AtspiEventDeleter>;
</span><ins>+using UniqueAtspiTextRange = std::unique_ptr<AtspiTextRange, AtspiTextRangeDeleter>;
</ins><span class="cx">
</span><span class="cx"> class AccessibilityTest : public WebViewTest {
</span><span class="cx"> public:
</span><span class="lines">@@ -98,9 +106,10 @@
</span><span class="cx"> m_eventSource = nullptr;
</span><span class="cx"> }
</span><span class="cx">
</span><del>- void startEventMonitor(AtspiAccessible* source, const Vector<const char*>& events)
</del><ins>+ void startEventMonitor(AtspiAccessible* source, Vector<CString>&& events)
</ins><span class="cx"> {
</span><span class="cx"> m_eventMonitor.source = source;
</span><ins>+ m_eventMonitor.eventTypes = WTFMove(events);
</ins><span class="cx"> m_eventMonitor.listener = adoptGRef(atspi_event_listener_new([](AtspiEvent* event, gpointer userData) {
</span><span class="cx"> auto* test = static_cast<AccessibilityTest*>(userData);
</span><span class="cx"> if (event->source == test->m_eventMonitor.source)
</span><span class="lines">@@ -107,8 +116,8 @@
</span><span class="cx"> test->m_eventMonitor.events.append(static_cast<AtspiEvent*>(g_boxed_copy(ATSPI_TYPE_EVENT, event)));
</span><span class="cx"> }, this, nullptr));
</span><span class="cx">
</span><del>- for (const auto* event : events)
- atspi_event_listener_register(m_eventMonitor.listener.get(), event, nullptr);
</del><ins>+ for (const auto& event : m_eventMonitor.eventTypes)
+ atspi_event_listener_register(m_eventMonitor.listener.get(), event.data(), nullptr);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> Vector<UniqueAtspiEvent> stopEventMonitor(unsigned expectedEvents, std::optional<Seconds> timeout = std::nullopt)
</span><span class="lines">@@ -119,10 +128,22 @@
</span><span class="cx"> g_main_context_iteration(nullptr, timeout ? FALSE : TRUE);
</span><span class="cx">
</span><span class="cx"> auto events = WTFMove(m_eventMonitor.events);
</span><del>- m_eventMonitor = { nullptr, { }, nullptr };
</del><ins>+ for (const auto& event : m_eventMonitor.eventTypes)
+ atspi_event_listener_deregister(m_eventMonitor.listener.get(), event.data(), nullptr);
+ m_eventMonitor = { nullptr, { }, { }, nullptr };
</ins><span class="cx"> return events;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ static AtspiEvent* findEvent(const Vector<UniqueAtspiEvent>& events, const char* eventType)
+ {
+ for (const auto& event : events) {
+ if (!g_strcmp0(event->type, eventType))
+ return event.get();
+ }
+
+ return nullptr;
+ }
+
</ins><span class="cx"> static unsigned stateSetSize(AtspiStateSet* stateSet)
</span><span class="cx"> {
</span><span class="cx"> GArray* states = atspi_state_set_get_states(stateSet);
</span><span class="lines">@@ -136,6 +157,7 @@
</span><span class="cx">
</span><span class="cx"> struct {
</span><span class="cx"> GRefPtr<AtspiEventListener> listener;
</span><ins>+ Vector<CString> eventTypes;
</ins><span class="cx"> Vector<UniqueAtspiEvent> events;
</span><span class="cx"> AtspiAccessible* source { nullptr };
</span><span class="cx"> } m_eventMonitor;
</span><span class="lines">@@ -805,6 +827,694 @@
</span><span class="cx"> }
</span><span class="cx"> #endif
</span><span class="cx">
</span><ins>+static void testTextBasic(AccessibilityTest* test, gconstpointer)
+{
+ test->showInWindow();
+ test->loadHtml(
+ "<html>"
+ " <body>"
+ " <p>This is a line of text</p>"
+ " </body>"
+ "</html>",
+ nullptr);
+ test->waitUntilLoadFinished();
+
+ auto testApp = test->findTestApplication();
+ g_assert_true(ATSPI_IS_ACCESSIBLE(testApp.get()));
+
+ auto documentWeb = test->findDocumentWeb(testApp.get());
+ g_assert_true(ATSPI_IS_ACCESSIBLE(documentWeb.get()));
+ g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 1);
+
+ auto p = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_TEXT(p.get()));
+ auto length = atspi_text_get_character_count(ATSPI_TEXT(p.get()), nullptr);
+ g_assert_cmpint(length, ==, 22);
+ GUniquePtr<char> text(atspi_text_get_text(ATSPI_TEXT(p.get()), 0, length, nullptr));
+ g_assert_cmpstr(text.get(), ==, "This is a line of text");
+ text.reset(atspi_text_get_text(ATSPI_TEXT(p.get()), 0, -1, nullptr));
+ g_assert_cmpstr(text.get(), ==, "This is a line of text");
+ text.reset(atspi_text_get_text(ATSPI_TEXT(p.get()), 5, 7, nullptr));
+ g_assert_cmpstr(text.get(), ==, "is");
+ text.reset(atspi_text_get_text(ATSPI_TEXT(p.get()), 0, -2, nullptr));
+ g_assert_cmpstr(text.get(), ==, "");
+ text.reset(atspi_text_get_text(ATSPI_TEXT(p.get()), -5, 23, nullptr));
+ g_assert_cmpstr(text.get(), ==, "");
+}
+
+static void testTextSurrogatePair(AccessibilityTest* test, gconstpointer)
+{
+#if USE(ATK)
+ g_test_skip("Surrogate pairs are not correctly handled by WebKit when using ATK");
+ return;
+#endif
+
+ test->showInWindow();
+ test->loadHtml(
+ "<html>"
+ " <body>"
+ " <p>This contains a 𝌆 symbol</p>"
+ " <input value='This contains a 𝌆 symbol'/>"
+ " </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 p = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_TEXT(p.get()));
+ auto length = atspi_text_get_character_count(ATSPI_TEXT(p.get()), nullptr);
+ g_assert_cmpint(length, ==, 24);
+ GUniquePtr<char> text(atspi_text_get_text(ATSPI_TEXT(p.get()), 0, length, nullptr));
+ g_assert_cmpstr(text.get(), ==, "This contains a 𝌆 symbol");
+ text.reset(atspi_text_get_text(ATSPI_TEXT(p.get()), 16, 17, nullptr));
+ g_assert_cmpstr(text.get(), ==, "𝌆");
+
+ 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);
+ auto 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, ==, 24);
+ text.reset(atspi_text_get_text(ATSPI_TEXT(input.get()), 0, length, nullptr));
+ g_assert_cmpstr(text.get(), ==, "This contains a 𝌆 symbol");
+ text.reset(atspi_text_get_text(ATSPI_TEXT(input.get()), 16, 17, nullptr));
+ g_assert_cmpstr(text.get(), ==, "𝌆");
+}
+
+static void testTextIterator(AccessibilityTest* test, gconstpointer)
+{
+ test->showInWindow(800, 600);
+ test->loadHtml(
+ "<html>"
+ " <body>"
+ " <p>Text of first sentence. This is the second sentence.<br>And this is the next paragraph.</p>"
+ " </body>"
+ "</html>",
+ nullptr);
+ test->waitUntilLoadFinished();
+
+ auto testApp = test->findTestApplication();
+ g_assert_true(ATSPI_IS_ACCESSIBLE(testApp.get()));
+
+ auto documentWeb = test->findDocumentWeb(testApp.get());
+ g_assert_true(ATSPI_IS_ACCESSIBLE(documentWeb.get()));
+ g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 1);
+
+ auto p = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_TEXT(p.get()));
+ auto length = atspi_text_get_character_count(ATSPI_TEXT(p.get()), nullptr);
+ g_assert_cmpint(length, ==, 84);
+ GUniquePtr<char> text(atspi_text_get_text(ATSPI_TEXT(p.get()), 0, length, nullptr));
+ g_assert_cmpstr(text.get(), ==, "Text of first sentence. This is the second sentence.\nAnd this is the next paragraph.");
+
+ // Character granularity.
+ UniqueAtspiTextRange range(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 0, ATSPI_TEXT_GRANULARITY_CHAR, nullptr));
+ g_assert_cmpstr(range->content, ==, "T");
+ g_assert_cmpint(range->start_offset, ==, 0);
+ g_assert_cmpint(range->end_offset, ==, 1);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 83, ATSPI_TEXT_GRANULARITY_CHAR, nullptr));
+ g_assert_cmpstr(range->content, ==, ".");
+ g_assert_cmpint(range->start_offset, ==, 83);
+ g_assert_cmpint(range->end_offset, ==, 84);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 84, ATSPI_TEXT_GRANULARITY_CHAR, nullptr));
+ g_assert_cmpstr(range->content, ==, "");
+ g_assert_cmpint(range->start_offset, ==, 84);
+ g_assert_cmpint(range->end_offset, ==, 84);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 85, ATSPI_TEXT_GRANULARITY_CHAR, nullptr));
+ g_assert_cmpstr(range->content, ==, "");
+#if USE(ATSPI)
+ g_assert_cmpint(range->start_offset, ==, 0);
+ g_assert_cmpint(range->end_offset, ==, 0);
+#else
+ // FIXME: ATK returns wrong offsets in this case.
+ g_assert_cmpint(range->start_offset, ==, 84);
+ g_assert_cmpint(range->end_offset, ==, 84);
+#endif
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), -1, ATSPI_TEXT_GRANULARITY_CHAR, nullptr));
+ g_assert_cmpstr(range->content, ==, "");
+ g_assert_cmpint(range->start_offset, ==, 0);
+ g_assert_cmpint(range->end_offset, ==, 0);
+
+ // Word granularity.
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 0, ATSPI_TEXT_GRANULARITY_WORD, nullptr));
+ g_assert_cmpstr(range->content, ==, "Text ");
+ g_assert_cmpint(range->start_offset, ==, 0);
+ g_assert_cmpint(range->end_offset, ==, 5);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 4, ATSPI_TEXT_GRANULARITY_WORD, nullptr));
+ g_assert_cmpstr(range->content, ==, "Text ");
+ g_assert_cmpint(range->start_offset, ==, 0);
+ g_assert_cmpint(range->end_offset, ==, 5);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 5, ATSPI_TEXT_GRANULARITY_WORD, nullptr));
+ g_assert_cmpstr(range->content, ==, "of ");
+ g_assert_cmpint(range->start_offset, ==, 5);
+ g_assert_cmpint(range->end_offset, ==, 8);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 40, ATSPI_TEXT_GRANULARITY_WORD, nullptr));
+ g_assert_cmpstr(range->content, ==, "second ");
+ g_assert_cmpint(range->start_offset, ==, 36);
+ g_assert_cmpint(range->end_offset, ==, 43);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 16, ATSPI_TEXT_GRANULARITY_WORD, nullptr));
+ g_assert_cmpstr(range->content, ==, "sentence. ");
+ g_assert_cmpint(range->start_offset, ==, 14);
+ g_assert_cmpint(range->end_offset, ==, 24);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 74, ATSPI_TEXT_GRANULARITY_WORD, nullptr));
+ g_assert_cmpstr(range->content, ==, "paragraph.");
+ g_assert_cmpint(range->start_offset, ==, 74);
+ g_assert_cmpint(range->end_offset, ==, 84);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 83, ATSPI_TEXT_GRANULARITY_WORD, nullptr));
+ g_assert_cmpstr(range->content, ==, "paragraph.");
+ g_assert_cmpint(range->start_offset, ==, 74);
+ g_assert_cmpint(range->end_offset, ==, 84);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 80, ATSPI_TEXT_GRANULARITY_WORD, nullptr));
+ g_assert_cmpstr(range->content, ==, "paragraph.");
+ g_assert_cmpint(range->start_offset, ==, 74);
+ g_assert_cmpint(range->end_offset, ==, 84);
+
+ // Sentence granularity.
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 0, ATSPI_TEXT_GRANULARITY_SENTENCE, nullptr));
+ g_assert_cmpstr(range->content, ==, "Text of first sentence. ");
+ g_assert_cmpint(range->start_offset, ==, 0);
+ g_assert_cmpint(range->end_offset, ==, 24);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 23, ATSPI_TEXT_GRANULARITY_SENTENCE, nullptr));
+ g_assert_cmpstr(range->content, ==, "Text of first sentence. ");
+ g_assert_cmpint(range->start_offset, ==, 0);
+ g_assert_cmpint(range->end_offset, ==, 24);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 24, ATSPI_TEXT_GRANULARITY_SENTENCE, nullptr));
+ g_assert_cmpstr(range->content, ==, "This is the second sentence.\n");
+ g_assert_cmpint(range->start_offset, ==, 24);
+ g_assert_cmpint(range->end_offset, ==, 53);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 40, ATSPI_TEXT_GRANULARITY_SENTENCE, nullptr));
+ g_assert_cmpstr(range->content, ==, "This is the second sentence.\n");
+ g_assert_cmpint(range->start_offset, ==, 24);
+ g_assert_cmpint(range->end_offset, ==, 53);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 53, ATSPI_TEXT_GRANULARITY_SENTENCE, nullptr));
+ g_assert_cmpstr(range->content, ==, "And this is the next paragraph.");
+ g_assert_cmpint(range->start_offset, ==, 53);
+ g_assert_cmpint(range->end_offset, ==, 84);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 83, ATSPI_TEXT_GRANULARITY_SENTENCE, nullptr));
+ g_assert_cmpstr(range->content, ==, "And this is the next paragraph.");
+ g_assert_cmpint(range->start_offset, ==, 53);
+ g_assert_cmpint(range->end_offset, ==, 84);
+
+ // Line granularity.
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 0, ATSPI_TEXT_GRANULARITY_LINE, nullptr));
+ g_assert_cmpstr(range->content, ==, "Text of first sentence. This is the second sentence.\n");
+ g_assert_cmpint(range->start_offset, ==, 0);
+ g_assert_cmpint(range->end_offset, ==, 53);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 83, ATSPI_TEXT_GRANULARITY_LINE, nullptr));
+ g_assert_cmpstr(range->content, ==, "And this is the next paragraph.");
+ g_assert_cmpint(range->start_offset, ==, 53);
+ g_assert_cmpint(range->end_offset, ==, 84);
+
+#if USE(ATSPI) // ATK doesn't implement paragraph granularity.
+ // Paragraph granularity.
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 0, ATSPI_TEXT_GRANULARITY_PARAGRAPH, nullptr));
+ g_assert_cmpstr(range->content, ==, "Text of first sentence. This is the second sentence.");
+ g_assert_cmpint(range->start_offset, ==, 0);
+ g_assert_cmpint(range->end_offset, ==, 52);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(p.get()), 83, ATSPI_TEXT_GRANULARITY_PARAGRAPH, nullptr));
+ g_assert_cmpstr(range->content, ==, "And this is the next paragraph.");
+ g_assert_cmpint(range->start_offset, ==, 53);
+ g_assert_cmpint(range->end_offset, ==, 84);
+#endif
+
+ // Using a text control now.
+ test->loadHtml(
+ "<html>"
+ " <body>"
+ " <input value='Text of input field'/>"
+ " </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);
+
+ auto section = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_ACCESSIBLE(section.get()));
+ g_assert_cmpint(atspi_accessible_get_child_count(section.get(), nullptr), ==, 1);
+ auto 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, ==, 19);
+ text.reset(atspi_text_get_text(ATSPI_TEXT(input.get()), 0, length, nullptr));
+ g_assert_cmpstr(text.get(), ==, "Text of input field");
+
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(input.get()), 0, ATSPI_TEXT_GRANULARITY_CHAR, nullptr));
+ g_assert_cmpstr(range->content, ==, "T");
+ 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()), 18, ATSPI_TEXT_GRANULARITY_CHAR, nullptr));
+ g_assert_cmpstr(range->content, ==, "d");
+ g_assert_cmpint(range->start_offset, ==, 18);
+ g_assert_cmpint(range->end_offset, ==, 19);
+
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(input.get()), 0, ATSPI_TEXT_GRANULARITY_WORD, nullptr));
+ g_assert_cmpstr(range->content, ==, "Text ");
+ g_assert_cmpint(range->start_offset, ==, 0);
+ g_assert_cmpint(range->end_offset, ==, 5);
+ range.reset(atspi_text_get_string_at_offset(ATSPI_TEXT(input.get()), 16, ATSPI_TEXT_GRANULARITY_WORD, nullptr));
+ g_assert_cmpstr(range->content, ==, "field");
+ g_assert_cmpint(range->start_offset, ==, 14);
+ g_assert_cmpint(range->end_offset, ==, 19);
+}
+
+static void testTextExtents(AccessibilityTest* test, gconstpointer)
+{
+ test->showInWindow();
+ test->loadHtml(
+ "<html>"
+ " <body>"
+ " <p>Text</p>"
+ " </body>"
+ "</html>",
+ nullptr);
+ test->waitUntilLoadFinished();
+
+ auto testApp = test->findTestApplication();
+ g_assert_true(ATSPI_IS_ACCESSIBLE(testApp.get()));
+
+ auto documentWeb = test->findDocumentWeb(testApp.get());
+ g_assert_true(ATSPI_IS_ACCESSIBLE(documentWeb.get()));
+ g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 1);
+
+ auto p = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_TEXT(p.get()));
+ GUniquePtr<AtspiRect> firstCharRect(atspi_text_get_character_extents(ATSPI_TEXT(p.get()), 0, ATSPI_COORD_TYPE_WINDOW, nullptr));
+ g_assert_nonnull(firstCharRect.get());
+ g_assert_cmpuint(firstCharRect->x, >, 0);
+ g_assert_cmpuint(firstCharRect->y, >, 0);
+ g_assert_cmpuint(firstCharRect->width, >, 0);
+ g_assert_cmpuint(firstCharRect->height, >, 0);
+ GUniquePtr<AtspiRect> lastCharRect(atspi_text_get_character_extents(ATSPI_TEXT(p.get()), 3, ATSPI_COORD_TYPE_WINDOW, nullptr));
+ g_assert_nonnull(lastCharRect.get());
+ g_assert_cmpuint(lastCharRect->x, >, firstCharRect->x);
+ g_assert_cmpuint(lastCharRect->y, ==, firstCharRect->y);
+ g_assert_cmpuint(lastCharRect->width, >, 0);
+ g_assert_cmpuint(lastCharRect->height, >, 0);
+ GUniquePtr<AtspiRect> rangeRect(atspi_text_get_range_extents(ATSPI_TEXT(p.get()), 0, 4, ATSPI_COORD_TYPE_WINDOW, nullptr));
+ g_assert_nonnull(rangeRect.get());
+ g_assert_cmpuint(rangeRect->x, ==, firstCharRect->x);
+ g_assert_cmpuint(rangeRect->y, ==, firstCharRect->y);
+ g_assert_cmpuint(rangeRect->width, >, 0);
+ g_assert_cmpuint(rangeRect->height, >, 0);
+#if USE(ATSPI) // Offset at point is broken with ATK.
+ auto offset = atspi_text_get_offset_at_point(ATSPI_TEXT(p.get()), firstCharRect->x, firstCharRect->y, ATSPI_COORD_TYPE_WINDOW, nullptr);
+ g_assert_cmpint(offset, ==, 0);
+ offset = atspi_text_get_offset_at_point(ATSPI_TEXT(p.get()), firstCharRect->x + firstCharRect->width, firstCharRect->y, ATSPI_COORD_TYPE_WINDOW, nullptr);
+ g_assert_cmpint(offset, ==, 1);
+ offset = atspi_text_get_offset_at_point(ATSPI_TEXT(p.get()), lastCharRect->x, lastCharRect->y, ATSPI_COORD_TYPE_WINDOW, nullptr);
+ g_assert_cmpint(offset, ==, 3);
+ offset = atspi_text_get_offset_at_point(ATSPI_TEXT(p.get()), lastCharRect->x + lastCharRect->width, lastCharRect->y, ATSPI_COORD_TYPE_WINDOW, nullptr);
+ g_assert_cmpint(offset, ==, 4);
+#endif
+}
+
+static void testTextSelections(AccessibilityTest* test, gconstpointer)
+{
+ test->showInWindow(800, 600);
+ test->loadHtml(
+ "<html>"
+ " <body>"
+ " <p>This is a line of text</p>"
+ " <input value='This is text input value'/>"
+ " </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 p = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_TEXT(p.get()));
+
+ auto selectionCount = atspi_text_get_n_selections(ATSPI_TEXT(p.get()), nullptr);
+ g_assert_cmpuint(selectionCount, ==, 0);
+ auto caretOffset = atspi_text_get_caret_offset(ATSPI_TEXT(p.get()), nullptr);
+#if USE(ATSPI)
+ g_assert_cmpint(caretOffset, ==, -1);
+#else
+ g_assert_cmpint(caretOffset, ==, 0);
+#endif
+
+ GUniquePtr<AtspiRange> selection(atspi_text_get_selection(ATSPI_TEXT(p.get()), 0, nullptr));
+ g_assert_nonnull(selection.get());
+ g_assert_cmpint(selection->start_offset, ==, 0);
+ g_assert_cmpint(selection->end_offset, ==, 0);
+
+ g_assert_true(atspi_text_set_selection(ATSPI_TEXT(p.get()), 0, 5, 14, nullptr));
+ selectionCount = atspi_text_get_n_selections(ATSPI_TEXT(p.get()), nullptr);
+ g_assert_cmpuint(selectionCount, ==, 1);
+ caretOffset = atspi_text_get_caret_offset(ATSPI_TEXT(p.get()), nullptr);
+ g_assert_cmpint(caretOffset, ==, 14);
+ selection.reset(atspi_text_get_selection(ATSPI_TEXT(p.get()), 0, nullptr));
+ g_assert_nonnull(selection.get());
+ g_assert_cmpint(selection->start_offset, ==, 5);
+ g_assert_cmpint(selection->end_offset, ==, 14);
+ GUniquePtr<char> text(atspi_text_get_text(ATSPI_TEXT(p.get()), selection->start_offset, selection->end_offset, nullptr));
+ g_assert_cmpstr(text.get(), ==, "is a line");
+
+ g_assert_true(atspi_text_remove_selection(ATSPI_TEXT(p.get()), 0, nullptr));
+ selectionCount = atspi_text_get_n_selections(ATSPI_TEXT(p.get()), nullptr);
+ g_assert_cmpuint(selectionCount, ==, 0);
+ caretOffset = atspi_text_get_caret_offset(ATSPI_TEXT(p.get()), nullptr);
+ g_assert_cmpint(caretOffset, ==, 14);
+ g_assert_true(atspi_text_set_caret_offset(ATSPI_TEXT(p.get()), 0, nullptr));
+ caretOffset = atspi_text_get_caret_offset(ATSPI_TEXT(p.get()), nullptr);
+ g_assert_cmpint(caretOffset, ==, 0);
+
+ 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);
+ 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));
+ caretOffset = atspi_text_get_caret_offset(ATSPI_TEXT(input.get()), nullptr);
+ g_assert_cmpint(caretOffset, ==, 5);
+ g_assert_true(atspi_text_set_selection(ATSPI_TEXT(input.get()), 0, 5, 12, nullptr));
+ selectionCount = atspi_text_get_n_selections(ATSPI_TEXT(input.get()), nullptr);
+ g_assert_cmpuint(selectionCount, ==, 1);
+ caretOffset = atspi_text_get_caret_offset(ATSPI_TEXT(input.get()), nullptr);
+ g_assert_cmpint(caretOffset, ==, 12);
+ selection.reset(atspi_text_get_selection(ATSPI_TEXT(input.get()), 0, nullptr));
+ g_assert_nonnull(selection.get());
+ g_assert_cmpint(selection->start_offset, ==, 5);
+ 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");
+}
+
+static void testTextAttributes(AccessibilityTest* test, gconstpointer)
+{
+ test->showInWindow(800, 600);
+ test->loadHtml(
+ "<html>"
+ " <body>"
+ " <p>This is a <b>line</b> of text</p>"
+ " </body>"
+ "</html>",
+ nullptr);
+ test->waitUntilLoadFinished();
+
+ auto testApp = test->findTestApplication();
+ g_assert_true(ATSPI_IS_ACCESSIBLE(testApp.get()));
+
+ auto documentWeb = test->findDocumentWeb(testApp.get());
+ g_assert_true(ATSPI_IS_ACCESSIBLE(documentWeb.get()));
+ g_assert_cmpint(atspi_accessible_get_child_count(documentWeb.get(), nullptr), ==, 1);
+
+ auto p = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_TEXT(p.get()));
+
+ // Including default attributes.
+ int startOffset, endOffset;
+ GRefPtr<GHashTable> attributes = adoptGRef(atspi_text_get_attribute_run(ATSPI_TEXT(p.get()), 0, TRUE, &startOffset, &endOffset, nullptr));
+ g_assert_nonnull(attributes.get());
+ g_assert_cmpuint(g_hash_table_size(attributes.get()), >, 1);
+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "weight")), ==, "400");
+ g_assert_cmpint(startOffset, ==, 0);
+ g_assert_cmpint(endOffset, ==, 10);
+
+ attributes = adoptGRef(atspi_text_get_attribute_run(ATSPI_TEXT(p.get()), 12, TRUE, &startOffset, &endOffset, nullptr));
+ g_assert_nonnull(attributes.get());
+ g_assert_cmpuint(g_hash_table_size(attributes.get()), >, 1);
+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "weight")), ==, "700");
+ GUniquePtr<char> value(atspi_text_get_attribute_value(ATSPI_TEXT(p.get()), 11, const_cast<char*>("weight"), nullptr));
+ g_assert_cmpstr(value.get(), ==, "700");
+ g_assert_cmpint(startOffset, ==, 10);
+ g_assert_cmpint(endOffset, ==, 14);
+
+ attributes = adoptGRef(atspi_text_get_attribute_run(ATSPI_TEXT(p.get()), 16, TRUE, &startOffset, &endOffset, nullptr));
+ g_assert_nonnull(attributes.get());
+ g_assert_cmpuint(g_hash_table_size(attributes.get()), >, 1);
+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "weight")), ==, "400");
+ g_assert_cmpint(startOffset, ==, 14);
+ g_assert_cmpint(endOffset, ==, 22);
+
+ // Without default attributes.
+ attributes = adoptGRef(atspi_text_get_attribute_run(ATSPI_TEXT(p.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, ==, 10);
+
+ attributes = adoptGRef(atspi_text_get_attribute_run(ATSPI_TEXT(p.get()), 12, FALSE, &startOffset, &endOffset, nullptr));
+ g_assert_nonnull(attributes.get());
+ g_assert_cmpuint(g_hash_table_size(attributes.get()), ==, 1);
+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "weight")), ==, "700");
+ g_assert_cmpint(startOffset, ==, 10);
+ g_assert_cmpint(endOffset, ==, 14);
+
+ attributes = adoptGRef(atspi_text_get_attribute_run(ATSPI_TEXT(p.get()), 16, FALSE, &startOffset, &endOffset, nullptr));
+ g_assert_nonnull(attributes.get());
+ g_assert_cmpuint(g_hash_table_size(attributes.get()), ==, 0);
+ g_assert_cmpint(startOffset, ==, 14);
+ g_assert_cmpint(endOffset, ==, 22);
+
+ // Only default attributes.
+ attributes = adoptGRef(atspi_text_get_default_attributes(ATSPI_TEXT(p.get()), nullptr));
+ g_assert_nonnull(attributes.get());
+ g_assert_cmpuint(g_hash_table_size(attributes.get()), >, 1);
+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "weight")), ==, "400");
+
+#if USE(ATSPI)
+ // Atspi implementation handles ranges with the same attributes as one run.
+ test->loadHtml(
+ "<html>"
+ " <body>"
+ " <p>This is a <b>li</b><b>ne</b> of text</p>"
+ " </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);
+
+ p = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_TEXT(p.get()));
+
+ attributes = adoptGRef(atspi_text_get_attribute_run(ATSPI_TEXT(p.get()), 0, TRUE, &startOffset, &endOffset, nullptr));
+ g_assert_nonnull(attributes.get());
+ g_assert_cmpuint(g_hash_table_size(attributes.get()), >, 1);
+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "weight")), ==, "400");
+ g_assert_cmpint(startOffset, ==, 0);
+ g_assert_cmpint(endOffset, ==, 10);
+
+ attributes = adoptGRef(atspi_text_get_attribute_run(ATSPI_TEXT(p.get()), 12, TRUE, &startOffset, &endOffset, nullptr));
+ g_assert_nonnull(attributes.get());
+ g_assert_cmpuint(g_hash_table_size(attributes.get()), >, 1);
+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "weight")), ==, "700");
+ g_assert_cmpint(startOffset, ==, 10);
+ g_assert_cmpint(endOffset, ==, 14);
+
+ attributes = adoptGRef(atspi_text_get_attribute_run(ATSPI_TEXT(p.get()), 16, TRUE, &startOffset, &endOffset, nullptr));
+ g_assert_nonnull(attributes.get());
+ g_assert_cmpuint(g_hash_table_size(attributes.get()), >, 1);
+ g_assert_cmpstr(static_cast<const char*>(g_hash_table_lookup(attributes.get(), "weight")), ==, "400");
+ g_assert_cmpint(startOffset, ==, 14);
+ g_assert_cmpint(endOffset, ==, 22);
+
+#endif
+}
+
+static void testTextStateChanged(AccessibilityTest* test, gconstpointer)
+{
+ test->showInWindow(800, 600);
+ test->loadHtml(
+ "<html>"
+ " <body>"
+ " <p>This is a line of text</p>"
+ " <input value='This is a text field'/>"
+ " <div contenteditable=true>This is content editable</div>"
+ " </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), ==, 3);
+
+ // Text caret moved.
+ auto p = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_TEXT(p.get()));
+ test->startEventMonitor(p.get(), { "object:text-caret-moved" });
+ g_assert_true(atspi_text_set_caret_offset(ATSPI_TEXT(p.get()), 10, nullptr));
+ auto 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, ==, 10);
+
+ 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);
+ auto input = adoptGRef(atspi_accessible_get_child_at_index(section.get(), 0, nullptr));
+ g_assert_true(ATSPI_IS_TEXT(input.get()));
+ test->startEventMonitor(input.get(), { "object:text-caret-moved" });
+ g_assert_true(atspi_text_set_caret_offset(ATSPI_TEXT(input.get()), 5, 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, ==, 5);
+
+ auto div = adoptGRef(atspi_accessible_get_child_at_index(documentWeb.get(), 2, nullptr));
+ g_assert_true(ATSPI_IS_TEXT(div.get()));
+ test->startEventMonitor(div.get(), { "object:text-caret-moved" });
+ g_assert_true(atspi_text_set_caret_offset(ATSPI_TEXT(div.get()), 15, 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, ==, 15);
+
+#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" });
+ g_assert_true(atspi_text_set_selection(ATSPI_TEXT(p.get()), 0, 10, 15, nullptr));
+ events = test->stopEventMonitor(2);
+ g_assert_cmpuint(events.size(), ==, 2);
+ auto* event = AccessibilityTest::findEvent(events, "object:text-caret-moved");
+ g_assert_nonnull(event);
+ g_assert_cmpuint(event->detail1, ==, 15);
+ g_assert_nonnull(AccessibilityTest::findEvent(events, "object:text-selection-changed"));
+
+ test->startEventMonitor(input.get(), { "object:text-selection-changed", "object:text-caret-moved" });
+ g_assert_true(atspi_text_set_selection(ATSPI_TEXT(input.get()), 0, 5, 10, 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, ==, 10);
+ g_assert_nonnull(AccessibilityTest::findEvent(events, "object:text-selection-changed"));
+
+ test->startEventMonitor(div.get(), { "object:text-selection-changed", "object:text-caret-moved" });
+ g_assert_true(atspi_text_set_selection(ATSPI_TEXT(div.get()), 0, 15, 18, 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, ==, 18);
+ g_assert_nonnull(AccessibilityTest::findEvent(events, "object:text-selection-changed"));
+#endif
+
+ // Text changed.
+ g_assert_true(atspi_text_set_caret_offset(ATSPI_TEXT(input.get()), 5, nullptr));
+ test->startEventMonitor(input.get(), { "object:text-changed:insert", "object:text-changed:delete" });
+ test->keyStroke(GDK_KEY_a);
+ events = test->stopEventMonitor(1);
+ g_assert_cmpuint(events.size(), ==, 1);
+ g_assert_cmpstr(events[0]->type, ==, "object:text-changed:insert");
+#if USE(ATSPI)
+ g_assert_cmpuint(events[0]->detail1, ==, 5);
+#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), ==, "a");
+ g_assert_true(atspi_text_set_caret_offset(ATSPI_TEXT(input.get()), 0, nullptr));
+ test->startEventMonitor(input.get(), { "object:text-changed:insert", "object:text-changed:delete" });
+ test->keyStroke(GDK_KEY_Delete);
+ events = test->stopEventMonitor(1);
+ g_assert_cmpuint(events.size(), ==, 1);
+ 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), ==, "T");
+
+ g_assert_true(atspi_text_set_caret_offset(ATSPI_TEXT(div.get()), 10, nullptr));
+ test->startEventMonitor(div.get(), { "object:text-changed:insert", "object:text-changed:delete" });
+ test->keyStroke(GDK_KEY_b);
+ events = test->stopEventMonitor(1);
+ g_assert_cmpuint(events.size(), ==, 1);
+ g_assert_cmpstr(events[0]->type, ==, "object:text-changed:insert");
+#if USE(ATSPI)
+ g_assert_cmpuint(events[0]->detail1, ==, 10);
+#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), ==, "b");
+ g_assert_true(atspi_text_set_caret_offset(ATSPI_TEXT(div.get()), 15, nullptr));
+ test->startEventMonitor(div.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, ==, 14);
+#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), ==, "n");
+
+ // 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" });
+ test->keyStroke(GDK_KEY_c);
+ 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, ==, 6);
+ g_assert_cmpuint(events[0]->detail2, ==, 5);
+ g_assert_true(G_VALUE_HOLDS_STRING(&events[0]->any_data));
+ g_assert_cmpstr(g_value_get_string(&events[0]->any_data), ==, "is a ");
+ g_assert_cmpstr(events[1]->type, ==, "object:text-changed:insert");
+#if USE(ATSPI)
+ g_assert_cmpuint(events[1]->detail1, ==, 5);
+#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), ==, "c");
+
+ g_assert_true(atspi_text_set_selection(ATSPI_TEXT(div.get()), 0, 10, 18, nullptr));
+ test->startEventMonitor(div.get(), { "object:text-changed:insert", "object:text-changed:delete" });
+ test->keyStroke(GDK_KEY_Delete);
+ 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, ==, 10);
+#endif
+ g_assert_cmpuint(events[0]->detail2, ==, 8);
+ g_assert_true(G_VALUE_HOLDS_STRING(&events[0]->any_data));
+ g_assert_cmpstr(g_value_get_string(&events[0]->any_data), ==, "bntet ed");
+
+#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);
+ 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, ==, 16);
+ g_assert_true(G_VALUE_HOLDS_STRING(&events[0]->any_data));
+ g_assert_cmpstr(g_value_get_string(&events[0]->any_data), ==, "his actext field");
+ g_assert_cmpstr(events[1]->type, ==, "object:text-changed:insert");
+ g_assert_cmpuint(events[1]->detail1, ==, 0);
+ 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");
+#endif
+}
+
</ins><span class="cx"> void beforeAll()
</span><span class="cx"> {
</span><span class="cx"> AccessibilityTest::add("WebKitAccessibility", "accessible/basic-hierarchy", testAccessibleBasicHierarchy);
</span><span class="lines">@@ -817,6 +1527,13 @@
</span><span class="cx"> #ifdef ATSPI_SCROLLTYPE_COUNT
</span><span class="cx"> AccessibilityTest::add("WebKitAccessibility", "component/scroll-to", testComponentScrollTo);
</span><span class="cx"> #endif
</span><ins>+ AccessibilityTest::add("WebKitAccessibility", "text/basic", testTextBasic);
+ AccessibilityTest::add("WebKitAccessibility", "text/surrogate-pair", testTextSurrogatePair);
+ AccessibilityTest::add("WebKitAccessibility", "text/iterator", testTextIterator);
+ AccessibilityTest::add("WebKitAccessibility", "text/extents", testTextExtents);
+ AccessibilityTest::add("WebKitAccessibility", "text/selections", testTextSelections);
+ AccessibilityTest::add("WebKitAccessibility", "text/attributes", testTextAttributes);
+ AccessibilityTest::add("WebKitAccessibility", "text/state-changed", testTextStateChanged);
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> void afterAll()
</span></span></pre>
</div>
</div>
</body>
</html>