Accessibility text search and selection API enhancements.
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 23 Apr 2019 20:31:51 +0000 (20:31 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 23 Apr 2019 20:31:51 +0000 (20:31 +0000)
https://bugs.webkit.org/show_bug.cgi?id=197095
<rdar://problem/48181791>

Patch by Andres Gonzalez <andresg_22@apple.com> on 2019-04-23
Reviewed by Chris Fleizach.

Source/WebCore:

- Split the existing SelectTextWithCriteria API into two: search text API (SearchTextWithCriteria) and a text operation API (TextOperation: select, replace, capitalize...).
- This allows for more flexibility and extensibility.
- Added the ability to retrieve text markers for multiple search hits.
- Various code clean up and consolidation.
- Added LayoutTest for search API.
- Previous API is marked with "To be deprecated", and is implemented with new implementation. May be removed in a future change.

Test: accessibility/mac/search-text/search-text.html

* accessibility/AccessibilityObject.cpp:
(WebCore::rangeClosestToRange):
(WebCore::AccessibilityObject::rangeOfStringClosestToRangeInDirection const):
(WebCore::AccessibilityObject::findTextRange const):
(WebCore::AccessibilityObject::findTextRanges const):
(WebCore::AccessibilityObject::performTextOperation):
(WebCore::AccessibilityObject::frame const):
(WebCore::AccessibilityObject::selectText): Deleted.
* accessibility/AccessibilityObject.h:
(WebCore::AccessibilitySearchTextCriteria::AccessibilitySearchTextCriteria):
(WebCore::AccessibilityTextOperation::AccessibilityTextOperation):
(WebCore::AccessibilitySelectTextCriteria::AccessibilitySelectTextCriteria): Deleted.
* accessibility/mac/WebAccessibilityObjectWrapperMac.mm:
(accessibilityTextCriteriaForParameterizedAttribute):
(accessibilitySearchTextCriteriaForParameterizedAttribute):
(accessibilityTextOperationForParameterizedAttribute):
(-[WebAccessibilityObjectWrapper IGNORE_WARNINGS_END]):
(-[WebAccessibilityObjectWrapper accessibilityAttributeValue:forParameter:]):
(accessibilitySelectTextCriteriaForCriteriaParameterizedAttribute): Deleted.

Tools:

Added new API JS binding code for searchTextWithCriteria to both WTR and DRT.

* DumpRenderTree/AccessibilityTextMarker.h:
* DumpRenderTree/AccessibilityUIElement.cpp:
(searchTextWithCriteriaCallback):
(AccessibilityUIElement::getJSClass):
* DumpRenderTree/AccessibilityUIElement.h:
* DumpRenderTree/mac/AccessibilityUIElementMac.mm:
(convertVectorToObjectArray):
(convertNSArrayToVector):
(searchTextParameterizedAttributeForCriteria):
(AccessibilityUIElement::getLinkedUIElements):
(AccessibilityUIElement::getDocumentLinks):
(AccessibilityUIElement::getChildren):
(AccessibilityUIElement::getChildrenWithRange):
(AccessibilityUIElement::rowHeaders const):
(AccessibilityUIElement::columnHeaders const):
(AccessibilityUIElement::uiElementArrayAttributeValue const):
(AccessibilityUIElement::searchTextWithCriteria):
(AccessibilityUIElement::attributesOfColumnHeaders):
(AccessibilityUIElement::attributesOfRowHeaders):
(AccessibilityUIElement::attributesOfColumns):
(AccessibilityUIElement::attributesOfRows):
(AccessibilityUIElement::attributesOfVisibleCells):
* WebKitTestRunner/InjectedBundle/AccessibilityUIElement.h:
* WebKitTestRunner/InjectedBundle/Bindings/AccessibilityUIElement.idl:
* WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm:
(WTR::convertVectorToObjectArray):
(WTR::convertNSArrayToVector):
(WTR::searchTextParameterizedAttributeForCriteria):
(WTR::AccessibilityUIElement::getLinkedUIElements):
(WTR::AccessibilityUIElement::getDocumentLinks):
(WTR::AccessibilityUIElement::getUIElementsWithAttribute const):
(WTR::AccessibilityUIElement::getChildren):
(WTR::AccessibilityUIElement::getChildrenWithRange):
(WTR::AccessibilityUIElement::rowHeaders const):
(WTR::AccessibilityUIElement::columnHeaders const):
(WTR::AccessibilityUIElement::uiElementArrayAttributeValue const):
(WTR::AccessibilityUIElement::searchTextWithCriteria):
(WTR::AccessibilityUIElement::attributesOfColumnHeaders):
(WTR::AccessibilityUIElement::attributesOfRowHeaders):
(WTR::AccessibilityUIElement::attributesOfColumns):
(WTR::AccessibilityUIElement::attributesOfRows):
(WTR::AccessibilityUIElement::attributesOfVisibleCells):
(WTR::convertElementsToObjectArray): Deleted.

LayoutTests:

- Added new test for AccessibilitySearchTextWithCriteria API.
- Updated bounds-for-range expected file that includes a list of available APIs.

* accessibility/mac/bounds-for-range-expected.txt:
* accessibility/mac/search-text/search-text-expected.txt: Added.
* accessibility/mac/search-text/search-text.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@244561 268f45cc-cd09-0410-ab3c-d52691b4dbfc

17 files changed:
LayoutTests/ChangeLog
LayoutTests/accessibility/mac/bounds-for-range-expected.txt
LayoutTests/accessibility/mac/search-text/search-text-expected.txt [new file with mode: 0644]
LayoutTests/accessibility/mac/search-text/search-text.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/accessibility/AccessibilityObject.cpp
Source/WebCore/accessibility/AccessibilityObject.h
Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm
Tools/ChangeLog
Tools/DumpRenderTree/AccessibilityTextMarker.h
Tools/DumpRenderTree/AccessibilityUIElement.cpp
Tools/DumpRenderTree/AccessibilityUIElement.h
Tools/DumpRenderTree/mac/AccessibilityUIElementMac.mm
Tools/WebKitTestRunner/InjectedBundle/AccessibilityUIElement.cpp
Tools/WebKitTestRunner/InjectedBundle/AccessibilityUIElement.h
Tools/WebKitTestRunner/InjectedBundle/Bindings/AccessibilityUIElement.idl
Tools/WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm

index 0e628f9..de1c18a 100644 (file)
@@ -1,3 +1,18 @@
+2019-04-23  Andres Gonzalez  <andresg_22@apple.com>
+
+        Accessibility text search and selection API enhancements.
+        https://bugs.webkit.org/show_bug.cgi?id=197095
+        <rdar://problem/48181791>
+
+        Reviewed by Chris Fleizach.
+
+        - Added new test for AccessibilitySearchTextWithCriteria API.
+        - Updated bounds-for-range expected file that includes a list of available APIs.
+
+        * accessibility/mac/bounds-for-range-expected.txt:
+        * accessibility/mac/search-text/search-text-expected.txt: Added.
+        * accessibility/mac/search-text/search-text.html: Added.
+
 2019-04-23  Guy Lewin  <guy@lewin.co.il>
 
         Multiple File Input Icon Set Regardless of File List
index b98455e..e39c1fa 100644 (file)
@@ -65,6 +65,8 @@ AXEndTextMarkerForBounds
 AXStartTextMarkerForBounds
 AXLineTextMarkerRangeForTextMarker
 AXSelectTextWithCriteria
+AXSearchTextWithCriteria
+AXTextOperation
 
 ----------------------
 {{-1.000000, -1.000000}, {60.000000, 18.000000}}
diff --git a/LayoutTests/accessibility/mac/search-text/search-text-expected.txt b/LayoutTests/accessibility/mac/search-text/search-text-expected.txt
new file mode 100644 (file)
index 0000000..01ca9f0
--- /dev/null
@@ -0,0 +1,36 @@
+The quick brown fox jumps over the lazy dog.
+
+TEXT2: The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
+
+This tests the ability to search for text given a starting point and a direction for the search.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS result.length is 1
+PASS text.stringForTextMarkerRange(result[0]) is 'lazy'
+PASS result.length is 1
+PASS text.stringForTextMarkerRange(result[0]) is 'quick'
+PASS result.length is 1
+PASS text.stringForTextMarkerRange(result[0]) is 'the'
+PASS result.length is 1
+PASS text.stringForTextMarkerRange(result[0]) is 'The'
+PASS result.length is 3
+PASS body.stringForTextMarkerRange(result[i]) is 'dog'
+PASS body.stringForTextMarkerRange(result[i]) is 'dog'
+PASS body.stringForTextMarkerRange(result[i]) is 'dog'
+PASS result.length is 3
+PASS body.stringForTextMarkerRange(result[i]) is 'fox'
+PASS body.stringForTextMarkerRange(result[i]) is 'fox'
+PASS body.stringForTextMarkerRange(result[i]) is 'fox'
+PASS typeof result is 'undefined'
+PASS result.length is 1
+PASS text.stringForTextMarkerRange(result[0]) is 'over'
+PASS result.length is 1
+PASS text.stringForTextMarkerRange(result[0]) is 'fox'
+PASS result.length is 1
+PASS text.stringForTextMarkerRange(result[0]) is 'fox'
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/accessibility/mac/search-text/search-text.html b/LayoutTests/accessibility/mac/search-text/search-text.html
new file mode 100644 (file)
index 0000000..2690d3f
--- /dev/null
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../../resources/js-test-pre.js"></script>
+<title>Search Text 1</title>
+</head>
+<body id="body">
+
+<p contenteditable="true" id="text">The quick brown fox <span id="target">jumps</span> over the lazy dog. </p>
+
+<p contenteditable="true" id="text2">TEXT2: The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.</p>
+
+<p id="description"></p>
+<div id="console"></div>
+
+<script>
+    description("This tests the ability to search for text given a starting point and a direction for the search.");
+
+    function selectElementText(element) {
+        var range = document.createRange();
+        range.selectNodeContents(element);
+        
+        var selection = window.getSelection();
+        selection.removeAllRanges();
+        selection.addRange(range);
+    }
+
+    if (window.accessibilityController) {
+        var text = accessibilityController.accessibleElementById("text");
+        var result = 0;
+        var selection = 0;
+        var target = document.getElementById("target");
+
+        // Select target element.
+        selectElementText(target);
+
+        // Search for text after selection (single search string).
+        result = text.searchTextWithCriteria("lazy", "AXSearchTextStartFromSelection", "AXSearchTextDirectionForward");
+        shouldBe("result.length", "1");
+        shouldBe("text.stringForTextMarkerRange(result[0])", "'lazy'");
+
+        // Search for text before selection (single search string).
+        result = text.searchTextWithCriteria("quick", "AXSearchTextStartFromSelection", "AXSearchTextDirectionBackward");
+        shouldBe("result.length", "1");
+        shouldBe("text.stringForTextMarkerRange(result[0])", "'quick'");
+
+        // Search for text closest to selection (single search string).
+        result = text.searchTextWithCriteria("the", "AXSearchTextStartFromSelection", "AXSearchTextDirectionClosest");
+        shouldBe("result.length", "1");
+        shouldBe("text.stringForTextMarkerRange(result[0])", "'the'");
+
+        // Search from the beginning (single search string).
+        result = text.searchTextWithCriteria("the", "AXSearchTextStartFromBegin", "AXSearchTextDirectionClosest");
+        shouldBe("result.length", "1");
+        shouldBe("text.stringForTextMarkerRange(result[0])", "'The'");
+
+        // Search for all occurrences from the beginning of document (single search string).
+        var body = accessibilityController.rootElement.childAtIndex(0);
+        result = body.searchTextWithCriteria("dog", "AXSearchTextStartFromBegin", "AXSearchTextDirectionAll");
+        shouldBe("result.length", "3");
+        for (var i = 0; i < result.length; i++)
+            shouldBe("body.stringForTextMarkerRange(result[i])", "'dog'");
+
+        // Search for all occurrences from the end of document (single search string).
+        result = body.searchTextWithCriteria("fox", "AXSearchTextStartFromEnd", "AXSearchTextDirectionAll");
+        shouldBe("result.length", "3");
+        for (var i = 0; i < result.length; i++)
+            shouldBe("body.stringForTextMarkerRange(result[i])", "'fox'");
+
+        // Search for a non-present string (single search string).
+        result = body.searchTextWithCriteria("cat", "AXSearchTextStartFromBegin", "AXSearchTextDirectionAll");
+        shouldBe("typeof result", "'undefined'");
+
+        // Search for multiple strings after selection.
+        result = text.searchTextWithCriteria(["lazy", "over"], "AXSearchTextStartFromSelection", "AXSearchTextDirectionForward");
+        shouldBe("result.length", "1");
+        shouldBe("text.stringForTextMarkerRange(result[0])", "'over'");
+
+        // Search for multiple strings before selection.
+        result = text.searchTextWithCriteria(["quick", "fox"], "AXSearchTextStartFromSelection", "AXSearchTextDirectionBackward");
+        shouldBe("result.length", "1");
+        shouldBe("text.stringForTextMarkerRange(result[0])", "'fox'");
+
+        // Search for multiple strings closest to selection.
+        result = text.searchTextWithCriteria(["dog", "fox"], "AXSearchTextStartFromSelection", "AXSearchTextDirectionClosest");
+        shouldBe("result.length", "1");
+        shouldBe("text.stringForTextMarkerRange(result[0])", "'fox'");
+    }
+</script>
+
+<script src="../../../resources/js-test-post.js"></script>
+</body>
+</html>
index d63878f..b70d5cd 100644 (file)
@@ -1,3 +1,40 @@
+2019-04-23  Andres Gonzalez  <andresg_22@apple.com>
+
+        Accessibility text search and selection API enhancements.
+        https://bugs.webkit.org/show_bug.cgi?id=197095
+        <rdar://problem/48181791>
+
+        Reviewed by Chris Fleizach.
+
+        - Split the existing SelectTextWithCriteria API into two: search text API (SearchTextWithCriteria) and a text operation API (TextOperation: select, replace, capitalize...).
+        - This allows for more flexibility and extensibility.
+        - Added the ability to retrieve text markers for multiple search hits.
+        - Various code clean up and consolidation.
+        - Added LayoutTest for search API.
+        - Previous API is marked with "To be deprecated", and is implemented with new implementation. May be removed in a future change.
+
+        Test: accessibility/mac/search-text/search-text.html
+
+        * accessibility/AccessibilityObject.cpp:
+        (WebCore::rangeClosestToRange):
+        (WebCore::AccessibilityObject::rangeOfStringClosestToRangeInDirection const):
+        (WebCore::AccessibilityObject::findTextRange const):
+        (WebCore::AccessibilityObject::findTextRanges const):
+        (WebCore::AccessibilityObject::performTextOperation):
+        (WebCore::AccessibilityObject::frame const):
+        (WebCore::AccessibilityObject::selectText): Deleted.
+        * accessibility/AccessibilityObject.h:
+        (WebCore::AccessibilitySearchTextCriteria::AccessibilitySearchTextCriteria):
+        (WebCore::AccessibilityTextOperation::AccessibilityTextOperation):
+        (WebCore::AccessibilitySelectTextCriteria::AccessibilitySelectTextCriteria): Deleted.
+        * accessibility/mac/WebAccessibilityObjectWrapperMac.mm:
+        (accessibilityTextCriteriaForParameterizedAttribute):
+        (accessibilitySearchTextCriteriaForParameterizedAttribute):
+        (accessibilityTextOperationForParameterizedAttribute):
+        (-[WebAccessibilityObjectWrapper IGNORE_WARNINGS_END]):
+        (-[WebAccessibilityObjectWrapper accessibilityAttributeValue:forParameter:]):
+        (accessibilitySelectTextCriteriaForCriteriaParameterizedAttribute): Deleted.
+
 2019-04-23  Guy Lewin  <guy@lewin.co.il>
 
         Multiple File Input Icon Set Regardless of File List
index 24d0496..b945596 100644 (file)
@@ -757,21 +757,21 @@ void AccessibilityObject::findMatchingObjects(AccessibilitySearchCriteria* crite
 // Returns the range that is fewer positions away from the reference range.
 // NOTE: The after range is expected to ACTUALLY be after the reference range and the before
 // range is expected to ACTUALLY be before. These are not checked for performance reasons.
-static RefPtr<Range> rangeClosestToRange(Range* referenceRange, RefPtr<Range>&& afterRange, RefPtr<Range>&& beforeRange)
+static RefPtr<Range> rangeClosestToRange(RefPtr<Range> const& referenceRange, RefPtr<Range>&& afterRange, RefPtr<Range>&& beforeRange)
 {
     if (!referenceRange)
         return nullptr;
-    
+
     // The treeScope for shadow nodes may not be the same scope as another element in a document.
     // Comparisons may fail in that case, which are expected behavior and should not assert.
     if (afterRange && (referenceRange->endPosition().isNull() || ((afterRange->startPosition().anchorNode()->compareDocumentPosition(*referenceRange->endPosition().anchorNode()) & Node::DOCUMENT_POSITION_DISCONNECTED) == Node::DOCUMENT_POSITION_DISCONNECTED)))
         return nullptr;
-    ASSERT(!afterRange || afterRange->startPosition() >= referenceRange->endPosition());
-    
+    ASSERT(!afterRange || afterRange->compareBoundaryPoints(Range::START_TO_START, *referenceRange).releaseReturnValue() >= 0);
+
     if (beforeRange && (referenceRange->startPosition().isNull() || ((beforeRange->endPosition().anchorNode()->compareDocumentPosition(*referenceRange->startPosition().anchorNode()) & Node::DOCUMENT_POSITION_DISCONNECTED) == Node::DOCUMENT_POSITION_DISCONNECTED)))
         return nullptr;
-    ASSERT(!beforeRange || beforeRange->endPosition() <= referenceRange->startPosition());
-    
+    ASSERT(!beforeRange || beforeRange->compareBoundaryPoints(Range::START_TO_START, *referenceRange).releaseReturnValue() <= 0);
+
     if (!afterRange && !beforeRange)
         return nullptr;
     if (afterRange && !beforeRange)
@@ -785,7 +785,7 @@ static RefPtr<Range> rangeClosestToRange(Range* referenceRange, RefPtr<Range>&&
     return positionsToAfterRange < positionsToBeforeRange ? afterRange : beforeRange;
 }
 
-RefPtr<Range> AccessibilityObject::rangeOfStringClosestToRangeInDirection(Range* referenceRange, AccessibilitySearchDirection searchDirection, Vector<String>& searchStrings) const
+RefPtr<Range> AccessibilityObject::rangeOfStringClosestToRangeInDirection(Range* referenceRange, AccessibilitySearchDirection searchDirection, Vector<String> const& searchStrings) const
 {
     Frame* frame = this->frame();
     if (!frame)
@@ -797,7 +797,7 @@ RefPtr<Range> AccessibilityObject::rangeOfStringClosestToRangeInDirection(Range*
     bool isBackwardSearch = searchDirection == AccessibilitySearchDirection::Previous;
     FindOptions findOptions { AtWordStarts, AtWordEnds, CaseInsensitive, StartInSelection };
     if (isBackwardSearch)
-        findOptions.add(Backwards);
+        findOptions.add(FindOptionFlag::Backwards);
     
     RefPtr<Range> closestStringRange = nullptr;
     for (const auto& searchString : searchStrings) {
@@ -843,87 +843,137 @@ RefPtr<Range> AccessibilityObject::elementRange() const
     return AXObjectCache::rangeForNodeContents(node());
 }
 
-String AccessibilityObject::selectText(AccessibilitySelectTextCriteria* criteria)
+RefPtr<Range> AccessibilityObject::findTextRange(Vector<String> const& searchStrings, RefPtr<Range> const& start, AccessibilitySearchTextDirection direction) const
 {
-    ASSERT(criteria);
-    
-    if (!criteria)
-        return String();
-    
-    Frame* frame = this->frame();
-    if (!frame)
-        return String();
-    
-    AccessibilitySelectTextActivity& activity = criteria->activity;
-    AccessibilitySelectTextAmbiguityResolution& ambiguityResolution = criteria->ambiguityResolution;
-    String& replacementString = criteria->replacementString;
-    Vector<String>& searchStrings = criteria->searchStrings;
-    
-    RefPtr<Range> selectedStringRange = selectionRange();
-    // When starting our search again, make this a zero length range so that search forwards will find this selected range if its appropriate.
-    selectedStringRange->setEnd(selectedStringRange->startContainer(), selectedStringRange->startOffset());
-    
-    RefPtr<Range> closestAfterStringRange = nullptr;
-    RefPtr<Range> closestBeforeStringRange = nullptr;
-    // Search forward if necessary.
-    if (ambiguityResolution == AccessibilitySelectTextAmbiguityResolution::ClosestAfter || ambiguityResolution == AccessibilitySelectTextAmbiguityResolution::ClosestTo)
-        closestAfterStringRange = rangeOfStringClosestToRangeInDirection(selectedStringRange.get(), AccessibilitySearchDirection::Next, searchStrings);
-    // Search backward if necessary.
-    if (ambiguityResolution == AccessibilitySelectTextAmbiguityResolution::ClosestBefore || ambiguityResolution == AccessibilitySelectTextAmbiguityResolution::ClosestTo)
-        closestBeforeStringRange = rangeOfStringClosestToRangeInDirection(selectedStringRange.get(), AccessibilitySearchDirection::Previous, searchStrings);
-    
-    // Determine which candidate is closest to the selection and perform the activity.
-    if (RefPtr<Range> closestStringRange = rangeClosestToRange(selectedStringRange.get(), WTFMove(closestAfterStringRange), WTFMove(closestBeforeStringRange))) {
+    RefPtr<Range> found;
+    if (direction == AccessibilitySearchTextDirection::Forward)
+        found = rangeOfStringClosestToRangeInDirection(start.get(), AccessibilitySearchDirection::Next, searchStrings);
+    else if (direction == AccessibilitySearchTextDirection::Backward)
+        found = rangeOfStringClosestToRangeInDirection(start.get(), AccessibilitySearchDirection::Previous, searchStrings);
+    else if (direction == AccessibilitySearchTextDirection::Closest) {
+        auto foundAfter = rangeOfStringClosestToRangeInDirection(start.get(), AccessibilitySearchDirection::Next, searchStrings);
+        auto foundBefore = rangeOfStringClosestToRangeInDirection(start.get(), AccessibilitySearchDirection::Previous, searchStrings);
+        found = rangeClosestToRange(start.get(), WTFMove(foundAfter), WTFMove(foundBefore));
+    }
+
+    if (found) {
         // If the search started within a text control, ensure that the result is inside that element.
         if (element() && element()->isTextField()) {
-            if (!closestStringRange->startContainer().isDescendantOrShadowDescendantOf(element()) || !closestStringRange->endContainer().isDescendantOrShadowDescendantOf(element()))
-                return String();
+            if (!found->startContainer().isDescendantOrShadowDescendantOf(element())
+                || !found->endContainer().isDescendantOrShadowDescendantOf(element()))
+                return nullptr;
         }
-        
-        String closestString = closestStringRange->text();
-        bool replaceSelection = false;
-        if (frame->selection().setSelectedRange(closestStringRange.get(), DOWNSTREAM, FrameSelection::ShouldCloseTyping::Yes)) {
-            switch (activity) {
-            case AccessibilitySelectTextActivity::FindAndCapitalize:
-                replacementString = capitalize(closestString, ' '); // FIXME: Needs to take locale into account to work correctly.
-                replaceSelection = true;
-                break;
-            case AccessibilitySelectTextActivity::FindAndUppercase:
-                replacementString = closestString.convertToUppercaseWithoutLocale(); // FIXME: Needs locale to work correctly.
-                replaceSelection = true;
-                break;
-            case AccessibilitySelectTextActivity::FindAndLowercase:
-                replacementString = closestString.convertToLowercaseWithoutLocale(); // FIXME: Needs locale to work correctly.
-                replaceSelection = true;
-                break;
-            case AccessibilitySelectTextActivity::FindAndReplace: {
-                replaceSelection = true;
-                // When applying find and replace activities, we want to match the capitalization of the replaced text,
-                // (unless we're replacing with an abbreviation.)
-                if (closestString.length() > 0 && replacementString.length() > 2 && replacementString != replacementString.convertToUppercaseWithoutLocale()) {
-                    if (closestString[0] == u_toupper(closestString[0]))
-                        replacementString = capitalize(replacementString, ' '); // FIXME: Needs to take locale into account to work correctly.
-                    else
-                        replacementString = replacementString.convertToLowercaseWithoutLocale(); // FIXME: Needs locale to work correctly.
-                }
-                break;
-            }
-            case AccessibilitySelectTextActivity::FindAndSelect:
-                break;
+    }
+    return found;
+}
+
+Vector<RefPtr<Range>> AccessibilityObject::findTextRanges(AccessibilitySearchTextCriteria const& criteria) const
+{
+    Vector<RefPtr<Range>> result;
+
+    // Determine start range.
+    RefPtr<Range> startRange;
+    if (criteria.start == AccessibilitySearchTextStartFrom::Selection)
+        startRange = selectionRange();
+    else
+        startRange = elementRange();
+
+    if (startRange) {
+        // Collapse the range to the start unless searching from the end of the doc or searching backwards.
+        if (criteria.start == AccessibilitySearchTextStartFrom::Begin)
+            startRange->collapse(true);
+        else if (criteria.start == AccessibilitySearchTextStartFrom::End)
+            startRange->collapse(false);
+        else
+            startRange->collapse(criteria.direction != AccessibilitySearchTextDirection::Backward);
+    } else
+        return result;
+
+    RefPtr<Range> found;
+    switch (criteria.direction) {
+    case AccessibilitySearchTextDirection::Forward:
+    case AccessibilitySearchTextDirection::Backward:
+    case AccessibilitySearchTextDirection::Closest:
+        found = findTextRange(criteria.searchStrings, startRange, criteria.direction);
+        if (found)
+            result.append(found);
+        break;
+    case AccessibilitySearchTextDirection::All: {
+        auto findAll = [&](AccessibilitySearchTextDirection dir) {
+            found = findTextRange(criteria.searchStrings, startRange, dir);
+            while (found) {
+                result.append(found);
+                found = findTextRange(criteria.searchStrings, found, dir);
             }
-            
-            // A bit obvious, but worth noting the API contract for this method is that we should
-            // return the replacement string when replacing, but the selected string if not.
-            if (replaceSelection) {
-                frame->editor().replaceSelectionWithText(replacementString, Editor::SelectReplacement::Yes, Editor::SmartReplace::Yes);
-                return replacementString;
+        };
+        findAll(AccessibilitySearchTextDirection::Forward);
+        findAll(AccessibilitySearchTextDirection::Backward);
+        break;
+    }
+    }
+
+    return result;
+}
+
+Vector<String> AccessibilityObject::performTextOperation(AccessibilityTextOperation const& operation)
+{
+    Vector<String> result;
+
+    if (operation.textRanges.isEmpty())
+        return result;
+
+    Frame* frame = this->frame();
+    if (!frame)
+        return result;
+
+    for (auto textRange : operation.textRanges) {
+        if (!frame->selection().setSelectedRange(textRange.get(), DOWNSTREAM, FrameSelection::ShouldCloseTyping::Yes))
+            continue;
+
+        String text = textRange->text();
+        String replacementString = operation.replacementText;
+        bool replaceSelection = false;
+        switch (operation.type) {
+        case AccessibilityTextOperationType::Capitalize:
+            replacementString = capitalize(text, ' '); // FIXME: Needs to take locale into account to work correctly.
+            replaceSelection = true;
+            break;
+        case AccessibilityTextOperationType::Uppercase:
+            replacementString = text.convertToUppercaseWithoutLocale(); // FIXME: Needs locale to work correctly.
+            replaceSelection = true;
+            break;
+        case AccessibilityTextOperationType::Lowercase:
+            replacementString = text.convertToLowercaseWithoutLocale(); // FIXME: Needs locale to work correctly.
+            replaceSelection = true;
+            break;
+        case AccessibilityTextOperationType::Replace: {
+            replaceSelection = true;
+            // When applying find and replace activities, we want to match the capitalization of the replaced text,
+            // (unless we're replacing with an abbreviation.)
+            if (text.length() > 0
+                && replacementString.length() > 2
+                && replacementString != replacementString.convertToUppercaseWithoutLocale()) {
+                if (text[0] == u_toupper(text[0]))
+                    replacementString = capitalize(replacementString, ' '); // FIXME: Needs to take locale into account to work correctly.
+                else
+                    replacementString = replacementString.convertToLowercaseWithoutLocale(); // FIXME: Needs locale to work correctly.
             }
-            
-            return closestString;
+            break;
+        }
+        case AccessibilityTextOperationType::Select:
+            break;
         }
+
+        // A bit obvious, but worth noting the API contract for this method is that we should
+        // return the replacement string when replacing, but the selected string if not.
+        if (replaceSelection) {
+            frame->editor().replaceSelectionWithText(replacementString, Editor::SelectReplacement::Yes, Editor::SmartReplace::Yes);
+            result.append(replacementString);
+        } else
+            result.append(text);
     }
-    
-    return String();
+
+    return result;
 }
 
 bool AccessibilityObject::hasAttributesRequiredForInclusion() const
@@ -1076,10 +1126,7 @@ bool AccessibilityObject::dispatchTouchEvent()
 Frame* AccessibilityObject::frame() const
 {
     Node* node = this->node();
-    if (!node)
-        return nullptr;
-    
-    return node->document().frame();
+    return node ? node->document().frame() : nullptr;
 }
 
 Frame* AccessibilityObject::mainFrame() const
index aec9c9b..bcc31e2 100644 (file)
@@ -305,30 +305,45 @@ struct PlainTextRange {
     bool isNull() const { return !start && !length; }
 };
 
-enum class AccessibilitySelectTextActivity {
-    FindAndReplace,
-    FindAndSelect,
-    FindAndCapitalize,
-    FindAndLowercase,
-    FindAndUppercase
+enum class AccessibilitySearchTextStartFrom {
+    Begin, // Search from the beginning of the element.
+    Selection, // Search from the position of the current selection.
+    End // Search from the end of the element.
 };
 
-enum class AccessibilitySelectTextAmbiguityResolution {
-    ClosestAfter,
-    ClosestBefore,
-    ClosestTo
+enum class AccessibilitySearchTextDirection {
+    Forward, // Occurrence after the starting range.
+    Backward, // Occurrence before the starting range.
+    Closest, // Closest occurrence to the starting range, whether after or before.
+    All // All occurrences
 };
 
-struct AccessibilitySelectTextCriteria {
-    AccessibilitySelectTextActivity activity;
-    AccessibilitySelectTextAmbiguityResolution ambiguityResolution;
-    String replacementString;
-    Vector<String> searchStrings;
-    
-    AccessibilitySelectTextCriteria(AccessibilitySelectTextActivity activity, AccessibilitySelectTextAmbiguityResolution ambiguityResolution, const String& replacementString)
-        : activity(activity)
-        , ambiguityResolution(ambiguityResolution)
-        , replacementString(replacementString)
+struct AccessibilitySearchTextCriteria {
+    Vector<String> searchStrings; // Text strings to search for.
+    AccessibilitySearchTextStartFrom start;
+    AccessibilitySearchTextDirection direction;
+
+    AccessibilitySearchTextCriteria()
+        : start(AccessibilitySearchTextStartFrom::Selection)
+        , direction(AccessibilitySearchTextDirection::Forward)
+    { }
+};
+
+enum class AccessibilityTextOperationType {
+    Select,
+    Replace,
+    Capitalize,
+    Lowercase,
+    Uppercase
+};
+
+struct AccessibilityTextOperation {
+    Vector<RefPtr<Range>> textRanges; // text on which perform the operation.
+    AccessibilityTextOperationType type;
+    String replacementText; // For type = replace.
+
+    AccessibilityTextOperation()
+        : type(AccessibilityTextOperationType::Select)
     { }
 };
 
@@ -607,12 +622,16 @@ public:
     virtual bool isDescendantOfBarrenParent() const { return false; }
 
     bool isDescendantOfRole(AccessibilityRole) const;
-    
+
     // Text selection
-    RefPtr<Range> rangeOfStringClosestToRangeInDirection(Range*, AccessibilitySearchDirection, Vector<String>&) const;
+private:
+    RefPtr<Range> rangeOfStringClosestToRangeInDirection(Range*, AccessibilitySearchDirection, Vector<String> const&) const;
     RefPtr<Range> selectionRange() const;
-    String selectText(AccessibilitySelectTextCriteria*);
-    
+    RefPtr<Range> findTextRange(Vector<String> const& searchStrings, RefPtr<Range> const& start, AccessibilitySearchTextDirection) const;
+public:
+    Vector<RefPtr<Range>> findTextRanges(AccessibilitySearchTextCriteria const&) const;
+    Vector<String> performTextOperation(AccessibilityTextOperation const&);
+
     virtual AccessibilityObject* observableObject() const { return nullptr; }
     virtual void linkedUIElements(AccessibilityChildrenVector&) const { }
     virtual AccessibilityObject* titleUIElement() const { return nullptr; }
index a03f765..38ca442 100644 (file)
@@ -372,6 +372,112 @@ using namespace HTMLNames;
 #define NSAccessibilitySelectTextWithCriteriaParameterizedAttribute @"AXSelectTextWithCriteria"
 #endif
 
+// Text search
+
+#ifndef NSAccessibilitySearchTextWithCriteriaParameterizedAttribute
+/* Performs a text search with the given parameters.
+ Returns an NSArray of text marker ranges of the search hits.
+ */
+#define NSAccessibilitySearchTextWithCriteriaParameterizedAttribute @"AXSearchTextWithCriteria"
+#endif
+
+#ifndef NSAccessibilitySearchTextSearchStrings
+// NSArray of strings to search for.
+#define NSAccessibilitySearchTextSearchStrings @"AXSearchTextSearchStrings"
+#endif
+
+#ifndef NSAccessibilitySearchTextStartFrom
+// NSString specifying the start point of the search: selection, begin or end.
+#define NSAccessibilitySearchTextStartFrom @"AXSearchTextStartFrom"
+#endif
+
+#ifndef NSAccessibilitySearchTextStartFromBegin
+// Value for SearchTextStartFrom.
+#define NSAccessibilitySearchTextStartFromBegin @"AXSearchTextStartFromBegin"
+#endif
+
+#ifndef NSAccessibilitySearchTextStartFromSelection
+// Value for SearchTextStartFrom.
+#define NSAccessibilitySearchTextStartFromSelection @"AXSearchTextStartFromSelection"
+#endif
+
+#ifndef NSAccessibilitySearchTextStartFromEnd
+// Value for SearchTextStartFrom.
+#define NSAccessibilitySearchTextStartFromEnd @"AXSearchTextStartFromEnd"
+#endif
+
+#ifndef NSAccessibilitySearchTextDirection
+// NSString specifying the direction of the search: forward, backward, closest, all.
+#define NSAccessibilitySearchTextDirection @"AXSearchTextDirection"
+#endif
+
+#ifndef NSAccessibilitySearchTextDirectionForward
+// Value for SearchTextDirection.
+#define NSAccessibilitySearchTextDirectionForward @"AXSearchTextDirectionForward"
+#endif
+
+#ifndef NSAccessibilitySearchTextDirectionBackward
+// Value for SearchTextDirection.
+#define NSAccessibilitySearchTextDirectionBackward @"AXSearchTextDirectionBackward"
+#endif
+
+#ifndef NSAccessibilitySearchTextDirectionClosest
+// Value for SearchTextDirection.
+#define NSAccessibilitySearchTextDirectionClosest @"AXSearchTextDirectionClosest"
+#endif
+
+#ifndef NSAccessibilitySearchTextDirectionAll
+// Value for SearchTextDirection.
+#define NSAccessibilitySearchTextDirectionAll @"AXSearchTextDirectionAll"
+#endif
+
+// Text operations
+
+#ifndef NSAccessibilityTextOperationParameterizedAttribute
+// Performs an operation on the given text.
+#define NSAccessibilityTextOperationParameterizedAttribute @"AXTextOperation"
+#endif
+
+#ifndef NSAccessibilityTextOperationMarkerRanges
+// Text on which to perform operation.
+#define NSAccessibilityTextOperationMarkerRanges @"AXTextOperationMarkerRanges"
+#endif
+
+#ifndef NSAccessibilityTextOperationType
+// The type of operation to be performed: select, replace, capitalize....
+#define NSAccessibilityTextOperationType @"AXTextOperationType"
+#endif
+
+#ifndef NSAccessibilityTextOperationSelect
+// Value for TextOperationType.
+#define NSAccessibilityTextOperationSelect @"TextOperationSelect"
+#endif
+
+#ifndef NSAccessibilityTextOperationReplace
+// Value for TextOperationType.
+#define NSAccessibilityTextOperationReplace @"TextOperationReplace"
+#endif
+
+#ifndef NSAccessibilityTextOperationCapitalize
+// Value for TextOperationType.
+#define NSAccessibilityTextOperationCapitalize @"Capitalize"
+#endif
+
+#ifndef NSAccessibilityTextOperationLowercase
+// Value for TextOperationType.
+#define NSAccessibilityTextOperationLowercase @"Lowercase"
+#endif
+
+#ifndef NSAccessibilityTextOperationUppercase
+// Value for TextOperationType.
+#define NSAccessibilityTextOperationUppercase @"Uppercase"
+#endif
+
+#ifndef NSAccessibilityTextOperationReplacementString
+// Replacement text for operation replace.
+#define NSAccessibilityTextOperationReplacementString @"AXTextOperationReplacementString"
+#endif
+
 // Math attributes
 #define NSAccessibilityMathRootRadicandAttribute @"AXMathRootRadicand"
 #define NSAccessibilityMathRootIndexAttribute @"AXMathRootIndex"
@@ -504,39 +610,39 @@ static id AXTextMarkerRangeEnd(id range)
 
 #pragma mark Select text helpers
 
-static AccessibilitySelectTextCriteria accessibilitySelectTextCriteriaForCriteriaParameterizedAttribute(const NSDictionary *parameterizedAttribute)
+// To be deprecated.
+static std::pair<AccessibilitySearchTextCriteria, AccessibilityTextOperation> accessibilityTextCriteriaForParameterizedAttribute(const NSDictionary *parameterizedAttribute)
 {
+    AccessibilitySearchTextCriteria criteria;
+    AccessibilityTextOperation operation;
+
     NSString *activityParameter = [parameterizedAttribute objectForKey:NSAccessibilitySelectTextActivity];
     NSString *ambiguityResolutionParameter = [parameterizedAttribute objectForKey:NSAccessibilitySelectTextAmbiguityResolution];
     NSString *replacementStringParameter = [parameterizedAttribute objectForKey:NSAccessibilitySelectTextReplacementString];
     NSArray *searchStringsParameter = [parameterizedAttribute objectForKey:NSAccessibilitySelectTextSearchStrings];
-    
-    AccessibilitySelectTextActivity activity = AccessibilitySelectTextActivity::FindAndSelect;
+
     if ([activityParameter isKindOfClass:[NSString class]]) {
         if ([activityParameter isEqualToString:NSAccessibilitySelectTextActivityFindAndReplace])
-            activity = AccessibilitySelectTextActivity::FindAndReplace;
+            operation.type = AccessibilityTextOperationType::Replace;
         else if ([activityParameter isEqualToString:kAXSelectTextActivityFindAndCapitalize])
-            activity = AccessibilitySelectTextActivity::FindAndCapitalize;
+            operation.type = AccessibilityTextOperationType::Capitalize;
         else if ([activityParameter isEqualToString:kAXSelectTextActivityFindAndLowercase])
-            activity = AccessibilitySelectTextActivity::FindAndLowercase;
+            operation.type = AccessibilityTextOperationType::Lowercase;
         else if ([activityParameter isEqualToString:kAXSelectTextActivityFindAndUppercase])
-            activity = AccessibilitySelectTextActivity::FindAndUppercase;
+            operation.type = AccessibilityTextOperationType::Uppercase;
     }
-    
-    AccessibilitySelectTextAmbiguityResolution ambiguityResolution = AccessibilitySelectTextAmbiguityResolution::ClosestTo;
+
+    criteria.direction = AccessibilitySearchTextDirection::Closest;
     if ([ambiguityResolutionParameter isKindOfClass:[NSString class]]) {
         if ([ambiguityResolutionParameter isEqualToString:NSAccessibilitySelectTextAmbiguityResolutionClosestAfterSelection])
-            ambiguityResolution = AccessibilitySelectTextAmbiguityResolution::ClosestAfter;
+            criteria.direction = AccessibilitySearchTextDirection::Forward;
         else if ([ambiguityResolutionParameter isEqualToString:NSAccessibilitySelectTextAmbiguityResolutionClosestBeforeSelection])
-            ambiguityResolution = AccessibilitySelectTextAmbiguityResolution::ClosestBefore;
+            criteria.direction = AccessibilitySearchTextDirection::Backward;
     }
-    
-    String replacementString;
+
     if ([replacementStringParameter isKindOfClass:[NSString class]])
-        replacementString = replacementStringParameter;
-    
-    AccessibilitySelectTextCriteria criteria(activity, ambiguityResolution, replacementString);
-    
+        operation.replacementText = replacementStringParameter;
+
     if ([searchStringsParameter isKindOfClass:[NSArray class]]) {
         size_t searchStringsCount = static_cast<size_t>([searchStringsParameter count]);
         criteria.searchStrings.reserveInitialCapacity(searchStringsCount);
@@ -545,10 +651,78 @@ static AccessibilitySelectTextCriteria accessibilitySelectTextCriteriaForCriteri
                 criteria.searchStrings.uncheckedAppend(searchString);
         }
     }
-    
+
+    return std::make_pair(criteria, operation);
+}
+
+static AccessibilitySearchTextCriteria accessibilitySearchTextCriteriaForParameterizedAttribute(const NSDictionary *params)
+{
+    AccessibilitySearchTextCriteria criteria;
+
+    NSArray *searchStrings = [params objectForKey:NSAccessibilitySearchTextSearchStrings];
+    NSString *start = [params objectForKey:NSAccessibilitySearchTextStartFrom];
+    NSString *direction = [params objectForKey:NSAccessibilitySearchTextDirection];
+
+    if ([searchStrings isKindOfClass:[NSArray class]]) {
+        size_t searchStringsCount = static_cast<size_t>([searchStrings count]);
+        criteria.searchStrings.reserveInitialCapacity(searchStringsCount);
+        for (NSString *searchString in searchStrings) {
+            if ([searchString isKindOfClass:[NSString class]])
+                criteria.searchStrings.uncheckedAppend(searchString);
+        }
+    }
+
+    if ([start isKindOfClass:[NSString class]]) {
+        if ([start isEqualToString:NSAccessibilitySearchTextStartFromBegin])
+            criteria.start = AccessibilitySearchTextStartFrom::Begin;
+        else if ([start isEqualToString:NSAccessibilitySearchTextStartFromEnd])
+            criteria.start = AccessibilitySearchTextStartFrom::End;
+    }
+
+    if ([direction isKindOfClass:[NSString class]]) {
+        if ([direction isEqualToString:NSAccessibilitySearchTextDirectionBackward])
+            criteria.direction = AccessibilitySearchTextDirection::Backward;
+        else if ([direction isEqualToString:NSAccessibilitySearchTextDirectionClosest])
+            criteria.direction = AccessibilitySearchTextDirection::Closest;
+        else if ([direction isEqualToString:NSAccessibilitySearchTextDirectionAll])
+            criteria.direction = AccessibilitySearchTextDirection::All;
+    }
+
     return criteria;
 }
 
+static AccessibilityTextOperation accessibilityTextOperationForParameterizedAttribute(WebAccessibilityObjectWrapper *obj, const NSDictionary *parameterizedAttribute)
+{
+    AccessibilityTextOperation operation;
+
+    NSArray *markerRanges = [parameterizedAttribute objectForKey:NSAccessibilityTextOperationMarkerRanges];
+    NSString *operationType = [parameterizedAttribute objectForKey:NSAccessibilityTextOperationType];
+    NSString *replacementString = [parameterizedAttribute objectForKey:NSAccessibilityTextOperationReplacementString];
+
+    if ([markerRanges isKindOfClass:[NSArray class]]) {
+        size_t count = static_cast<size_t>(markerRanges.count);
+        operation.textRanges.reserveInitialCapacity(count);
+        for (id markerRange : markerRanges)
+            operation.textRanges.append([obj rangeForTextMarkerRange:markerRange]);
+    }
+
+    if ([operationType isKindOfClass:[NSString class]]) {
+        if ([operationType isEqualToString:NSAccessibilityTextOperationReplace])
+            operation.type = AccessibilityTextOperationType::Replace;
+        else if ([operationType isEqualToString:NSAccessibilityTextOperationCapitalize])
+            operation.type = AccessibilityTextOperationType::Capitalize;
+        else if ([operationType isEqualToString:NSAccessibilityTextOperationLowercase])
+            operation.type = AccessibilityTextOperationType::Lowercase;
+        else if ([operationType isEqualToString:NSAccessibilityTextOperationUppercase])
+            operation.type = AccessibilityTextOperationType::Uppercase;
+    }
+
+    if ([replacementString isKindOfClass:[NSString class]])
+        operation.replacementText = replacementString;
+
+    return operation;
+}
+
 #pragma mark Text Marker helpers
 
 static BOOL getBytesFromAXTextMarker(CFTypeRef textMarker, void* bytes, size_t length)
@@ -3439,6 +3613,8 @@ IGNORE_WARNINGS_END
             NSAccessibilityStartTextMarkerForBoundsParameterizedAttribute,
             NSAccessibilityLineTextMarkerRangeForTextMarkerParameterizedAttribute,
             NSAccessibilitySelectTextWithCriteriaParameterizedAttribute,
+            NSAccessibilitySearchTextWithCriteriaParameterizedAttribute,
+            NSAccessibilityTextOperationParameterizedAttribute,
             nil];
     }
     
@@ -3990,10 +4166,41 @@ IGNORE_WARNINGS_END
     
     // dispatch
     if ([attribute isEqualToString:NSAccessibilitySelectTextWithCriteriaParameterizedAttribute]) {
-        AccessibilitySelectTextCriteria criteria = accessibilitySelectTextCriteriaForCriteriaParameterizedAttribute(dictionary);
-        return m_object->selectText(&criteria);
+        // To be deprecated.
+        auto criteria = accessibilityTextCriteriaForParameterizedAttribute(dictionary);
+        criteria.second.textRanges = m_object->findTextRanges(criteria.first);
+        ASSERT(criteria.second.textRanges.size() <= 1);
+        Vector<String> result = m_object->performTextOperation(criteria.second);
+        ASSERT(result.size() <= 1);
+        if (result.size() > 0)
+            return result[0];
+        return String();
+    }
+
+    if ([attribute isEqualToString:NSAccessibilitySearchTextWithCriteriaParameterizedAttribute]) {
+        auto criteria = accessibilitySearchTextCriteriaForParameterizedAttribute(dictionary);
+        auto ranges = m_object->findTextRanges(criteria);
+        if (ranges.isEmpty())
+            return nil;
+        NSMutableArray *markers = [NSMutableArray arrayWithCapacity:ranges.size()];
+        for (auto range : ranges) {
+            if (id marker = [self textMarkerRangeFromRange:range])
+                [markers addObject:marker];
+        }
+        return markers;
     }
-    
+
+    if ([attribute isEqualToString:NSAccessibilityTextOperationParameterizedAttribute]) {
+        auto textOperation = accessibilityTextOperationForParameterizedAttribute(self, dictionary);
+        auto operationResult = m_object->performTextOperation(textOperation);
+        if (operationResult.isEmpty())
+            return nil;
+        NSMutableArray *result = [NSMutableArray arrayWithCapacity:operationResult.size()];
+        for (auto str : operationResult)
+            [result addObject:str];
+        return result;
+    }
+
     if ([attribute isEqualToString:NSAccessibilityUIElementCountForSearchPredicateParameterizedAttribute]) {
         AccessibilitySearchCriteria criteria = accessibilitySearchCriteriaForSearchPredicateParameterizedAttribute(dictionary);
         AccessibilityObject::AccessibilityChildrenVector results;
index ffa2b02..bf91cfa 100644 (file)
@@ -1,3 +1,57 @@
+2019-04-23  Andres Gonzalez  <andresg_22@apple.com>
+
+        Accessibility text search and selection API enhancements.
+        https://bugs.webkit.org/show_bug.cgi?id=197095
+        <rdar://problem/48181791>
+
+        Reviewed by Chris Fleizach.
+
+        Added new API JS binding code for searchTextWithCriteria to both WTR and DRT.
+
+        * DumpRenderTree/AccessibilityTextMarker.h:
+        * DumpRenderTree/AccessibilityUIElement.cpp:
+        (searchTextWithCriteriaCallback):
+        (AccessibilityUIElement::getJSClass):
+        * DumpRenderTree/AccessibilityUIElement.h:
+        * DumpRenderTree/mac/AccessibilityUIElementMac.mm:
+        (convertVectorToObjectArray):
+        (convertNSArrayToVector):
+        (searchTextParameterizedAttributeForCriteria):
+        (AccessibilityUIElement::getLinkedUIElements):
+        (AccessibilityUIElement::getDocumentLinks):
+        (AccessibilityUIElement::getChildren):
+        (AccessibilityUIElement::getChildrenWithRange):
+        (AccessibilityUIElement::rowHeaders const):
+        (AccessibilityUIElement::columnHeaders const):
+        (AccessibilityUIElement::uiElementArrayAttributeValue const):
+        (AccessibilityUIElement::searchTextWithCriteria):
+        (AccessibilityUIElement::attributesOfColumnHeaders):
+        (AccessibilityUIElement::attributesOfRowHeaders):
+        (AccessibilityUIElement::attributesOfColumns):
+        (AccessibilityUIElement::attributesOfRows):
+        (AccessibilityUIElement::attributesOfVisibleCells):
+        * WebKitTestRunner/InjectedBundle/AccessibilityUIElement.h:
+        * WebKitTestRunner/InjectedBundle/Bindings/AccessibilityUIElement.idl:
+        * WebKitTestRunner/InjectedBundle/mac/AccessibilityUIElementMac.mm:
+        (WTR::convertVectorToObjectArray):
+        (WTR::convertNSArrayToVector):
+        (WTR::searchTextParameterizedAttributeForCriteria):
+        (WTR::AccessibilityUIElement::getLinkedUIElements):
+        (WTR::AccessibilityUIElement::getDocumentLinks):
+        (WTR::AccessibilityUIElement::getUIElementsWithAttribute const):
+        (WTR::AccessibilityUIElement::getChildren):
+        (WTR::AccessibilityUIElement::getChildrenWithRange):
+        (WTR::AccessibilityUIElement::rowHeaders const):
+        (WTR::AccessibilityUIElement::columnHeaders const):
+        (WTR::AccessibilityUIElement::uiElementArrayAttributeValue const):
+        (WTR::AccessibilityUIElement::searchTextWithCriteria):
+        (WTR::AccessibilityUIElement::attributesOfColumnHeaders):
+        (WTR::AccessibilityUIElement::attributesOfRowHeaders):
+        (WTR::AccessibilityUIElement::attributesOfColumns):
+        (WTR::AccessibilityUIElement::attributesOfRows):
+        (WTR::AccessibilityUIElement::attributesOfVisibleCells):
+        (WTR::convertElementsToObjectArray): Deleted.
+
 2019-04-23  Guy Lewin  <guy@lewin.co.il>
 
         Multiple File Input Icon Set Regardless of File List
index ec5f1fd..a34db8c 100644 (file)
@@ -75,9 +75,10 @@ public:
     
     static JSObjectRef makeJSAccessibilityTextMarkerRange(JSContextRef, const AccessibilityTextMarkerRange&);
     bool isEqual(AccessibilityTextMarkerRange*);
-    
-private:
+
     static JSClassRef getJSClass();
+
+private:
 #if SUPPORTS_AX_TEXTMARKERS && PLATFORM(MAC)
     RetainPtr<PlatformTextMarkerRange> m_textMarkerRange;
 #else
index c0b4dc4..1159cd6 100644 (file)
@@ -279,6 +279,24 @@ static JSValueRef selectTextWithCriteriaCallback(JSContextRef context, JSObjectR
     return JSValueMakeString(context, result.get());
 }
 
+#if PLATFORM(MAC)
+static JSValueRef searchTextWithCriteriaCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+    if (argumentCount < 1)
+        return JSValueMakeUndefined(context);
+
+    JSValueRef searchStrings = arguments[0];
+    JSRetainPtr<JSStringRef> startFrom;
+    if (argumentCount > 1)
+        startFrom = adopt(JSValueToStringCopy(context, arguments[1], exception));
+    JSRetainPtr<JSStringRef> direction;
+    if (argumentCount > 2)
+        direction = adopt(JSValueToStringCopy(context, arguments[2], exception));
+
+    return toAXElement(thisObject)->searchTextWithCriteria(context, searchStrings, startFrom.get(), direction.get());
+}
+#endif
+
 static JSValueRef indexOfChildCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
 {
     if (argumentCount != 1)
@@ -1896,6 +1914,9 @@ JSClassRef AccessibilityUIElement::getJSClass()
         { "uiElementCountForSearchPredicate", uiElementCountForSearchPredicateCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "uiElementForSearchPredicate", uiElementForSearchPredicateCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "selectTextWithCriteria", selectTextWithCriteriaCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
+#if PLATFORM(MAC)
+        { "searchTextWithCriteria", searchTextWithCriteriaCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
+#endif
         { "childAtIndex", childAtIndexCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "linkedUIElementAtIndex", linkedUIElementAtIndexCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
         { "indexOfChild", indexOfChildCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
index 3a7081f..0e2a1d3 100644 (file)
@@ -218,6 +218,10 @@ public:
     unsigned uiElementCountForSearchPredicate(JSContextRef, AccessibilityUIElement* startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly);
     AccessibilityUIElement uiElementForSearchPredicate(JSContextRef, AccessibilityUIElement* startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly);
     JSRetainPtr<JSStringRef> selectTextWithCriteria(JSContextRef, JSStringRef ambiguityResolution, JSValueRef searchStrings, JSStringRef replacementString, JSStringRef activity);
+#if PLATFORM(MAC)
+    JSValueRef searchTextWithCriteria(JSContextRef, JSValueRef searchStrings, JSStringRef StartFrom, JSStringRef direction);
+#endif
+
 #if PLATFORM(IOS_FAMILY)
     void elementsForRange(unsigned location, unsigned length, Vector<AccessibilityUIElement>& elements);
     JSRetainPtr<JSStringRef> stringForSelection();
index e956923..8024657 100644 (file)
@@ -178,6 +178,18 @@ static NSString* attributesOfElement(id accessibilityObject)
     return attributesString;
 }
 
+template<typename T>
+static JSValueRef convertVectorToObjectArray(JSContextRef context, Vector<T> const& elements)
+{
+    JSValueRef arrayResult = JSObjectMakeArray(context, 0, 0, 0);
+    JSObjectRef arrayObj = JSValueToObject(context, arrayResult, 0);
+    size_t elementCount = elements.size();
+    for (size_t i = 0; i < elementCount; ++i)
+        JSObjectSetPropertyAtIndex(context, arrayObj, i, JSObjectMake(context, elements[i].getJSClass(), new T(elements[i])), 0);
+
+    return arrayResult;
+}
+
 static JSRetainPtr<JSStringRef> concatenateAttributeAndValue(NSString *attribute, NSString *value)
 {
     Vector<UniChar> buffer([attribute length]);
@@ -192,11 +204,15 @@ static JSRetainPtr<JSStringRef> concatenateAttributeAndValue(NSString *attribute
     return adopt(JSStringCreateWithCharacters(buffer.data(), buffer.size()));
 }
 
-static void convertNSArrayToVector(NSArray* array, Vector<AccessibilityUIElement>& elementVector)
+template<typename T>
+static Vector<T> convertNSArrayToVector(NSArray *array)
 {
-    NSUInteger count = [array count];
+    Vector<T> v;
+    NSUInteger count = array.count;
+    v.reserveInitialCapacity(count);
     for (NSUInteger i = 0; i < count; ++i)
-        elementVector.append(AccessibilityUIElement([array objectAtIndex:i]));
+        v.append([array objectAtIndex:i]);
+    return v;
 }
 
 static JSRetainPtr<JSStringRef> descriptionOfElements(Vector<AccessibilityUIElement>& elementVector)
@@ -255,11 +271,50 @@ static NSDictionary *selectTextParameterizedAttributeForCriteria(JSContextRef co
     return parameterizedAttribute;
 }
 
+#if PLATFORM(MAC)
+static NSDictionary *searchTextParameterizedAttributeForCriteria(JSContextRef context, JSValueRef searchStrings, JSStringRef startFrom, JSStringRef direction)
+{
+    NSMutableDictionary *parameterizedAttribute = [NSMutableDictionary dictionary];
+
+    if (searchStrings) {
+        NSMutableArray *searchStringsParameter = [NSMutableArray array];
+        if (JSValueIsString(context, searchStrings)) {
+            auto searchStringsString = adopt(JSValueToStringCopy(context, searchStrings, nullptr));
+            if (searchStringsString)
+                [searchStringsParameter addObject:[NSString stringWithJSStringRef:searchStringsString.get()]];
+        } else if (JSValueIsObject(context, searchStrings)) {
+            JSObjectRef searchStringsArray = JSValueToObject(context, searchStrings, nullptr);
+            unsigned searchStringsArrayLength = 0;
+
+            auto lengthPropertyString = adopt(JSStringCreateWithUTF8CString("length"));
+            JSValueRef searchStringsArrayLengthValue = JSObjectGetProperty(context, searchStringsArray, lengthPropertyString.get(), nullptr);
+            if (searchStringsArrayLengthValue && JSValueIsNumber(context, searchStringsArrayLengthValue))
+                searchStringsArrayLength = static_cast<unsigned>(JSValueToNumber(context, searchStringsArrayLengthValue, nullptr));
+
+            for (unsigned i = 0; i < searchStringsArrayLength; ++i) {
+                auto searchStringsString = adopt(JSValueToStringCopy(context, JSObjectGetPropertyAtIndex(context, searchStringsArray, i, nullptr), nullptr));
+                if (searchStringsString)
+                    [searchStringsParameter addObject:[NSString stringWithJSStringRef:searchStringsString.get()]];
+            }
+        }
+        [parameterizedAttribute setObject:searchStringsParameter forKey:@"AXSearchTextSearchStrings"];
+    }
+
+    if (startFrom)
+        [parameterizedAttribute setObject:[NSString stringWithJSStringRef:startFrom] forKey:@"AXSearchTextStartFrom"];
+
+    if (direction)
+        [parameterizedAttribute setObject:[NSString stringWithJSStringRef:direction] forKey:@"AXSearchTextDirection"];
+
+    return parameterizedAttribute;
+}
+#endif
+
 void AccessibilityUIElement::getLinkedUIElements(Vector<AccessibilityUIElement>& elementVector)
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* linkedElements = [m_element accessibilityAttributeValue:NSAccessibilityLinkedUIElementsAttribute];
-    convertNSArrayToVector(linkedElements, elementVector);
+    elementVector = convertNSArrayToVector<AccessibilityUIElement>(linkedElements);
     END_AX_OBJC_EXCEPTIONS
 }
 
@@ -267,7 +322,7 @@ void AccessibilityUIElement::getDocumentLinks(Vector<AccessibilityUIElement>& el
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* linkElements = [m_element accessibilityAttributeValue:@"AXLinkUIElements"];
-    convertNSArrayToVector(linkElements, elementVector);
+    elementVector = convertNSArrayToVector<AccessibilityUIElement>(linkElements);
     END_AX_OBJC_EXCEPTIONS
 }
 
@@ -275,7 +330,7 @@ void AccessibilityUIElement::getChildren(Vector<AccessibilityUIElement>& element
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* children = [m_element accessibilityAttributeValue:NSAccessibilityChildrenAttribute];
-    convertNSArrayToVector(children, elementVector);
+    elementVector = convertNSArrayToVector<AccessibilityUIElement>(children);
     END_AX_OBJC_EXCEPTIONS
 }
 
@@ -283,7 +338,7 @@ void AccessibilityUIElement::getChildrenWithRange(Vector<AccessibilityUIElement>
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* children = [m_element accessibilityArrayAttributeValues:NSAccessibilityChildrenAttribute index:location maxCount:length];
-    convertNSArrayToVector(children, elementVector);
+    elementVector = convertNSArrayToVector<AccessibilityUIElement>(children);
     END_AX_OBJC_EXCEPTIONS
 }
 
@@ -492,7 +547,7 @@ void AccessibilityUIElement::rowHeaders(Vector<AccessibilityUIElement>& elements
     BEGIN_AX_OBJC_EXCEPTIONS
     id value = [m_element accessibilityAttributeValue:NSAccessibilityRowHeaderUIElementsAttribute];
     if ([value isKindOfClass:[NSArray class]])
-        convertNSArrayToVector(value, elements);
+        elements = convertNSArrayToVector<AccessibilityUIElement>(value);
     END_AX_OBJC_EXCEPTIONS
 }
 
@@ -501,7 +556,7 @@ void AccessibilityUIElement::columnHeaders(Vector<AccessibilityUIElement>& eleme
     BEGIN_AX_OBJC_EXCEPTIONS
     id value = [m_element accessibilityAttributeValue:NSAccessibilityColumnHeaderUIElementsAttribute];
     if ([value isKindOfClass:[NSArray class]])
-        convertNSArrayToVector(value, elements);
+        elements = convertNSArrayToVector<AccessibilityUIElement>(value);
     END_AX_OBJC_EXCEPTIONS
 }
 
@@ -510,7 +565,7 @@ void AccessibilityUIElement::uiElementArrayAttributeValue(JSStringRef attribute,
     BEGIN_AX_OBJC_EXCEPTIONS
     id value = [m_element accessibilityAttributeValue:[NSString stringWithJSStringRef:attribute]];
     if ([value isKindOfClass:[NSArray class]])
-        convertNSArrayToVector(value, elements);
+        elements = convertNSArrayToVector<AccessibilityUIElement>(value);
     END_AX_OBJC_EXCEPTIONS
 }
 
@@ -1103,13 +1158,25 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::selectTextWithCriteria(JSContex
     return nullptr;
 }
 
+#if PLATFORM(MAC)
+JSValueRef AccessibilityUIElement::searchTextWithCriteria(JSContextRef context, JSValueRef searchStrings, JSStringRef startFrom, JSStringRef direction)
+{
+    BEGIN_AX_OBJC_EXCEPTIONS
+    NSDictionary *parameterizedAttribute = searchTextParameterizedAttributeForCriteria(context, searchStrings, startFrom, direction);
+    id result = [m_element accessibilityAttributeValue:@"AXSearchTextWithCriteria" forParameter:parameterizedAttribute];
+    if ([result isKindOfClass:[NSArray class]])
+        return convertVectorToObjectArray<AccessibilityTextMarkerRange>(context, convertNSArrayToVector<AccessibilityTextMarkerRange>(result));
+    END_AX_OBJC_EXCEPTIONS
+    return nullptr;
+}
+#endif
+
 JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumnHeaders()
 {
     // not yet defined in AppKit... odd
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* columnHeadersArray = [m_element accessibilityAttributeValue:@"AXColumnHeaderUIElements"];
-    Vector<AccessibilityUIElement> columnHeadersVector;
-    convertNSArrayToVector(columnHeadersArray, columnHeadersVector);
+    auto columnHeadersVector = convertNSArrayToVector<AccessibilityUIElement>(columnHeadersArray);
     return descriptionOfElements(columnHeadersVector);
     END_AX_OBJC_EXCEPTIONS
     
@@ -1120,8 +1187,7 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRowHeaders()
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* rowHeadersArray = [m_element accessibilityAttributeValue:@"AXRowHeaderUIElements"];
-    Vector<AccessibilityUIElement> rowHeadersVector;
-    convertNSArrayToVector(rowHeadersArray, rowHeadersVector);
+    auto rowHeadersVector = convertNSArrayToVector<AccessibilityUIElement>(rowHeadersArray);
     return descriptionOfElements(rowHeadersVector);
     END_AX_OBJC_EXCEPTIONS
     
@@ -1132,8 +1198,7 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumns()
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* columnsArray = [m_element accessibilityAttributeValue:NSAccessibilityColumnsAttribute];
-    Vector<AccessibilityUIElement> columnsVector;
-    convertNSArrayToVector(columnsArray, columnsVector);
+    auto columnsVector = convertNSArrayToVector<AccessibilityUIElement>(columnsArray);
     return descriptionOfElements(columnsVector);
     END_AX_OBJC_EXCEPTIONS
     
@@ -1144,8 +1209,7 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRows()
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* rowsArray = [m_element accessibilityAttributeValue:NSAccessibilityRowsAttribute];
-    Vector<AccessibilityUIElement> rowsVector;
-    convertNSArrayToVector(rowsArray, rowsVector);
+    auto rowsVector = convertNSArrayToVector<AccessibilityUIElement>(rowsArray);
     return descriptionOfElements(rowsVector);
     END_AX_OBJC_EXCEPTIONS
     
@@ -1156,8 +1220,7 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfVisibleCells()
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* cellsArray = [m_element accessibilityAttributeValue:@"AXVisibleCells"];
-    Vector<AccessibilityUIElement> cellsVector;
-    convertNSArrayToVector(cellsArray, cellsVector);
+    auto cellsVector = convertNSArrayToVector<AccessibilityUIElement>(cellsArray);
     return descriptionOfElements(cellsVector);
     END_AX_OBJC_EXCEPTIONS
     
index a4f681c..73c1610 100644 (file)
@@ -91,6 +91,7 @@ RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::selectedTextMarkerR
 void AccessibilityUIElement::resetSelectedTextMarkerRange() { }
 void AccessibilityUIElement::setBoolAttributeValue(JSStringRef, bool) { }
 void AccessibilityUIElement::setValue(JSStringRef) { }
+JSValueRef AccessibilityUIElement::searchTextWithCriteria(JSContextRef, JSValueRef, JSStringRef, JSStringRef) { return nullptr; }
 #endif
 
 #if !PLATFORM(COCOA) || !HAVE(ACCESSIBILITY)
index dfa45ca..24c094b 100644 (file)
@@ -255,6 +255,7 @@ public:
     unsigned uiElementCountForSearchPredicate(JSContextRef, AccessibilityUIElement* startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly);
     RefPtr<AccessibilityUIElement> uiElementForSearchPredicate(JSContextRef, AccessibilityUIElement* startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly);
     JSRetainPtr<JSStringRef> selectTextWithCriteria(JSContextRef, JSStringRef ambiguityResolution, JSValueRef searchStrings, JSStringRef replacementString, JSStringRef activity);
+    JSValueRef searchTextWithCriteria(JSContextRef, JSValueRef searchStrings, JSStringRef startFrom, JSStringRef direction);
 
     // Text-specific
     JSRetainPtr<JSStringRef> characterAtOffset(int offset);
index 127e6a7..e3531fb 100644 (file)
@@ -183,6 +183,7 @@ interface AccessibilityUIElement {
     [PassContext] unsigned long uiElementCountForSearchPredicate(AccessibilityUIElement startElement, boolean isDirectionNext, object searchKey, DOMString searchText, boolean visibleOnly, boolean immediateDescendantsOnly);
     [PassContext] AccessibilityUIElement uiElementForSearchPredicate(AccessibilityUIElement startElement, boolean isDirectionNext, object searchKey, DOMString searchText, boolean visibleOnly, boolean immediateDescendantsOnly);
     [PassContext] DOMString selectTextWithCriteria(DOMString ambiguityResolution, object searchStrings, DOMString replacementString, DOMString activity);
+    [PassContext] object searchTextWithCriteria(object searchStrings, DOMString startFrom, DOMString direction);
     boolean setSelectedTextRange(unsigned long location, unsigned long length);
 
     // Scroll area attributes.
index c9b40d8..6b2788e 100644 (file)
@@ -192,14 +192,15 @@ static NSString* attributesOfElement(id accessibilityObject)
     return attributesString;
 }
 
-static JSValueRef convertElementsToObjectArray(JSContextRef context, Vector<RefPtr<AccessibilityUIElement>>& elements)
+template<typename T>
+static JSValueRef convertVectorToObjectArray(JSContextRef context, Vector<T> const& elements)
 {
     JSValueRef arrayResult = JSObjectMakeArray(context, 0, 0, 0);
     JSObjectRef arrayObj = JSValueToObject(context, arrayResult, 0);
     size_t elementCount = elements.size();
     for (size_t i = 0; i < elementCount; ++i)
         JSObjectSetPropertyAtIndex(context, arrayObj, i, JSObjectMake(context, elements[i]->wrapperClass(), elements[i].get()), 0);
-    
+
     return arrayResult;
 }
 
@@ -217,11 +218,15 @@ static JSRetainPtr<JSStringRef> concatenateAttributeAndValue(NSString* attribute
     return adopt(JSStringCreateWithCharacters(buffer.data(), buffer.size()));
 }
 
-static void convertNSArrayToVector(NSArray* array, Vector<RefPtr<AccessibilityUIElement> >& elementVector)
+template<typename T>
+static Vector<T> convertNSArrayToVector(NSArray *array)
 {
-    NSUInteger count = [array count];
+    Vector<T> v;
+    NSUInteger count = array.count;
+    v.reserveInitialCapacity(count);
     for (NSUInteger i = 0; i < count; ++i)
-        elementVector.append(AccessibilityUIElement::create([array objectAtIndex:i]));
+        v.append(T::ValueType::create([array objectAtIndex:i]));
+    return v;
 }
 
 static JSRetainPtr<JSStringRef> descriptionOfElements(Vector<RefPtr<AccessibilityUIElement> >& elementVector)
@@ -281,11 +286,48 @@ static NSDictionary *selectTextParameterizedAttributeForCriteria(JSContextRef co
     return parameterizedAttribute;
 }
 
+static NSDictionary *searchTextParameterizedAttributeForCriteria(JSContextRef context, JSValueRef searchStrings, JSStringRef startFrom, JSStringRef direction)
+{
+    NSMutableDictionary *parameterizedAttribute = [NSMutableDictionary dictionary];
+
+    if (searchStrings) {
+        NSMutableArray *searchStringsParameter = [NSMutableArray array];
+        if (JSValueIsString(context, searchStrings)) {
+            auto searchStringsString = adopt(JSValueToStringCopy(context, searchStrings, nullptr));
+            if (searchStringsString)
+                [searchStringsParameter addObject:[NSString stringWithJSStringRef:searchStringsString.get()]];
+        } else if (JSValueIsObject(context, searchStrings)) {
+            JSObjectRef searchStringsArray = JSValueToObject(context, searchStrings, nullptr);
+            unsigned searchStringsArrayLength = 0;
+
+            auto lengthPropertyString = adopt(JSStringCreateWithUTF8CString("length"));
+            JSValueRef searchStringsArrayLengthValue = JSObjectGetProperty(context, searchStringsArray, lengthPropertyString.get(), nullptr);
+            if (searchStringsArrayLengthValue && JSValueIsNumber(context, searchStringsArrayLengthValue))
+                searchStringsArrayLength = static_cast<unsigned>(JSValueToNumber(context, searchStringsArrayLengthValue, nullptr));
+
+            for (unsigned i = 0; i < searchStringsArrayLength; ++i) {
+                auto searchStringsString = adopt(JSValueToStringCopy(context, JSObjectGetPropertyAtIndex(context, searchStringsArray, i, nullptr), nullptr));
+                if (searchStringsString)
+                    [searchStringsParameter addObject:[NSString stringWithJSStringRef:searchStringsString.get()]];
+            }
+        }
+        [parameterizedAttribute setObject:searchStringsParameter forKey:@"AXSearchTextSearchStrings"];
+    }
+
+    if (startFrom)
+        [parameterizedAttribute setObject:[NSString stringWithJSStringRef:startFrom] forKey:@"AXSearchTextStartFrom"];
+
+    if (direction)
+        [parameterizedAttribute setObject:[NSString stringWithJSStringRef:direction] forKey:@"AXSearchTextDirection"];
+
+    return parameterizedAttribute;
+}
+
 void AccessibilityUIElement::getLinkedUIElements(Vector<RefPtr<AccessibilityUIElement> >& elementVector)
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* linkedElements = [m_element accessibilityAttributeValue:NSAccessibilityLinkedUIElementsAttribute];
-    convertNSArrayToVector(linkedElements, elementVector);
+    elementVector = convertNSArrayToVector<RefPtr<AccessibilityUIElement>>(linkedElements);
     END_AX_OBJC_EXCEPTIONS
 }
 
@@ -293,7 +335,7 @@ void AccessibilityUIElement::getDocumentLinks(Vector<RefPtr<AccessibilityUIEleme
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* linkElements = [m_element accessibilityAttributeValue:@"AXLinkUIElements"];
-    convertNSArrayToVector(linkElements, elementVector);
+    elementVector = convertNSArrayToVector<RefPtr<AccessibilityUIElement>>(linkElements);
     END_AX_OBJC_EXCEPTIONS
 }
 
@@ -302,7 +344,7 @@ void AccessibilityUIElement::getUIElementsWithAttribute(JSStringRef attribute, V
     BEGIN_AX_OBJC_EXCEPTIONS
     id value = [m_element accessibilityAttributeValue:[NSString stringWithJSStringRef:attribute]];
     if ([value isKindOfClass:[NSArray class]])
-        convertNSArrayToVector(value, elements);
+        elements = convertNSArrayToVector<RefPtr<AccessibilityUIElement>>(value);
     END_AX_OBJC_EXCEPTIONS
 }
 
@@ -310,7 +352,7 @@ void AccessibilityUIElement::getChildren(Vector<RefPtr<AccessibilityUIElement> >
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* children = [m_element accessibilityAttributeValue:NSAccessibilityChildrenAttribute];
-    convertNSArrayToVector(children, elementVector);
+    elementVector = convertNSArrayToVector<RefPtr<AccessibilityUIElement>>(children);
     END_AX_OBJC_EXCEPTIONS
 }
 
@@ -318,7 +360,7 @@ void AccessibilityUIElement::getChildrenWithRange(Vector<RefPtr<AccessibilityUIE
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* children = [m_element accessibilityArrayAttributeValues:NSAccessibilityChildrenAttribute index:location maxCount:length];
-    convertNSArrayToVector(children, elementVector);
+    elementVector = convertNSArrayToVector<RefPtr<AccessibilityUIElement>>(children);
     END_AX_OBJC_EXCEPTIONS
 }
 
@@ -331,8 +373,8 @@ JSValueRef AccessibilityUIElement::rowHeaders() const
     Vector<RefPtr<AccessibilityUIElement>> elements;
     id value = [m_element accessibilityAttributeValue:NSAccessibilityRowHeaderUIElementsAttribute];
     if ([value isKindOfClass:[NSArray class]])
-        convertNSArrayToVector(value, elements);
-    return convertElementsToObjectArray(context, elements);
+        elements = convertNSArrayToVector<RefPtr<AccessibilityUIElement>>(value);
+    return convertVectorToObjectArray<RefPtr<AccessibilityUIElement>>(context, elements);
     END_AX_OBJC_EXCEPTIONS
 }
 
@@ -345,8 +387,8 @@ JSValueRef AccessibilityUIElement::columnHeaders() const
     Vector<RefPtr<AccessibilityUIElement>> elements;
     id value = [m_element accessibilityAttributeValue:NSAccessibilityColumnHeaderUIElementsAttribute];
     if ([value isKindOfClass:[NSArray class]])
-        convertNSArrayToVector(value, elements);
-    return convertElementsToObjectArray(context, elements);
+        elements = convertNSArrayToVector<RefPtr<AccessibilityUIElement>>(value);
+    return convertVectorToObjectArray<RefPtr<AccessibilityUIElement>>(context, elements);
     END_AX_OBJC_EXCEPTIONS
 }
     
@@ -596,7 +638,7 @@ JSValueRef AccessibilityUIElement::uiElementArrayAttributeValue(JSStringRef attr
     
     Vector<RefPtr<AccessibilityUIElement>> elements;
     getUIElementsWithAttribute(attribute, elements);
-    return convertElementsToObjectArray(context, elements);
+    return convertVectorToObjectArray<RefPtr<AccessibilityUIElement>>(context, elements);
 }
 
 RefPtr<AccessibilityUIElement> AccessibilityUIElement::uiElementAttributeValue(JSStringRef attribute) const
@@ -1199,13 +1241,26 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::selectTextWithCriteria(JSContex
     return nullptr;
 }
 
+#if PLATFORM(MAC)
+JSValueRef AccessibilityUIElement::searchTextWithCriteria(JSContextRef context, JSValueRef searchStrings, JSStringRef startFrom, JSStringRef direction)
+{
+    BEGIN_AX_OBJC_EXCEPTIONS
+    NSDictionary *parameterizedAttribute = searchTextParameterizedAttributeForCriteria(context, searchStrings, startFrom, direction);
+    id result = [m_element accessibilityAttributeValue:@"AXSearchTextWithCriteria" forParameter:parameterizedAttribute];
+    if ([result isKindOfClass:[NSArray class]])
+        return convertVectorToObjectArray<RefPtr<AccessibilityTextMarkerRange>>(context, convertNSArrayToVector<RefPtr<AccessibilityTextMarkerRange>>(result));
+    END_AX_OBJC_EXCEPTIONS
+
+    return nullptr;
+}
+#endif
+
 JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumnHeaders()
 {
     // not yet defined in AppKit... odd
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* columnHeadersArray = [m_element accessibilityAttributeValue:@"AXColumnHeaderUIElements"];
-    Vector<RefPtr<AccessibilityUIElement> > columnHeadersVector;
-    convertNSArrayToVector(columnHeadersArray, columnHeadersVector);
+    auto columnHeadersVector = convertNSArrayToVector<RefPtr<AccessibilityUIElement>>(columnHeadersArray);
     return descriptionOfElements(columnHeadersVector);
     END_AX_OBJC_EXCEPTIONS
     
@@ -1216,8 +1271,7 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRowHeaders()
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* rowHeadersArray = [m_element accessibilityAttributeValue:@"AXRowHeaderUIElements"];
-    Vector<RefPtr<AccessibilityUIElement> > rowHeadersVector;
-    convertNSArrayToVector(rowHeadersArray, rowHeadersVector);
+    auto rowHeadersVector = convertNSArrayToVector<RefPtr<AccessibilityUIElement>>(rowHeadersArray);
     return descriptionOfElements(rowHeadersVector);
     END_AX_OBJC_EXCEPTIONS
     
@@ -1228,8 +1282,7 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumns()
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* columnsArray = [m_element accessibilityAttributeValue:NSAccessibilityColumnsAttribute];
-    Vector<RefPtr<AccessibilityUIElement> > columnsVector;
-    convertNSArrayToVector(columnsArray, columnsVector);
+    auto columnsVector = convertNSArrayToVector<RefPtr<AccessibilityUIElement>>(columnsArray);
     return descriptionOfElements(columnsVector);
     END_AX_OBJC_EXCEPTIONS
     
@@ -1240,8 +1293,7 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRows()
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* rowsArray = [m_element accessibilityAttributeValue:NSAccessibilityRowsAttribute];
-    Vector<RefPtr<AccessibilityUIElement> > rowsVector;
-    convertNSArrayToVector(rowsArray, rowsVector);
+    auto rowsVector = convertNSArrayToVector<RefPtr<AccessibilityUIElement>>(rowsArray);
     return descriptionOfElements(rowsVector);
     END_AX_OBJC_EXCEPTIONS
     
@@ -1252,8 +1304,7 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfVisibleCells()
 {
     BEGIN_AX_OBJC_EXCEPTIONS
     NSArray* cellsArray = [m_element accessibilityAttributeValue:@"AXVisibleCells"];
-    Vector<RefPtr<AccessibilityUIElement> > cellsVector;
-    convertNSArrayToVector(cellsArray, cellsVector);
+    auto cellsVector = convertNSArrayToVector<RefPtr<AccessibilityUIElement>>(cellsArray);
     return descriptionOfElements(cellsVector);
     END_AX_OBJC_EXCEPTIONS