Title: [211356] trunk
Revision
211356
Author
n_w...@apple.com
Date
2017-01-29 21:03:11 -0800 (Sun, 29 Jan 2017)

Log Message

AX: WKContentView needs to implement UITextInput methods to make speak selection highlighting work
https://bugs.webkit.org/show_bug.cgi?id=166955

Reviewed by Ryosuke Niwa.

Source/WebCore:

Created a new version of Range::collectSelectionRect() that returns rects for each
line, so that Speak Selection doesn't need to handle searching for soft line breaks.
Also added a variant of findPlainText to search for the closest matched range to the given position.

Test: editing/text-iterator/range-of-string-closest-to-position.html

* dom/Range.cpp:
(WebCore::Range::collectSelectionRectsWithoutUnionInteriorLines):
(WebCore::Range::collectSelectionRects):
* dom/Range.h:
* editing/TextIterator.cpp:
(WebCore::findPlainTextMatches):
(WebCore::updateSearchBuffer):
(WebCore::findIteratorOptions):
(WebCore::rangeMatches):
(WebCore::findClosestPlainText):
(WebCore::findPlainText):
(WebCore::findPlainTextOffset): Deleted.
* editing/TextIterator.h:
* editing/htmlediting.h:
* testing/Internals.cpp:
(WebCore::Internals::rangeOfStringNearLocation):
* testing/Internals.h:
* testing/Internals.idl:

Source/WebKit2:

Implemented methods that Speak Selection can use to retrieve the word/sentence highlighting rects.

* Scripts/webkit/messages.py:
(headers_for_type):
* UIProcess/WebPageProxy.h:
* UIProcess/WebPageProxy.messages.in:
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView webSelectionRectsForSelectionRects:]):
(-[WKContentView webSelectionRects]):
(-[WKContentView _accessibilityRetrieveRectsEnclosingSelectionOffset:withGranularity:]):
(-[WKContentView _accessibilityRetrieveRectsAtSelectionOffset:withText:]):
* UIProcess/ios/WebPageProxyIOS.mm:
(WebKit::WebPageProxy::selectionRectsCallback):
(WebKit::WebPageProxy::requestRectsForGranularityWithSelectionOffset):
(WebKit::WebPageProxy::requestRectsAtSelectionOffsetWithText):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::visiblePositionForPositionWithOffset):
(WebKit::WebPage::getRectsForGranularityWithSelectionOffset):
(WebKit::rangeNearPositionMatchesText):
(WebKit::WebPage::getRectsAtSelectionOffsetWithText):

LayoutTests:

* editing/text-iterator/range-of-string-closest-to-position-expected.txt: Added.
* editing/text-iterator/range-of-string-closest-to-position.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (211355 => 211356)


--- trunk/LayoutTests/ChangeLog	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/LayoutTests/ChangeLog	2017-01-30 05:03:11 UTC (rev 211356)
@@ -1,3 +1,13 @@
+2017-01-29  Nan Wang  <n_w...@apple.com>
+
+        AX: WKContentView needs to implement UITextInput methods to make speak selection highlighting work
+        https://bugs.webkit.org/show_bug.cgi?id=166955
+
+        Reviewed by Ryosuke Niwa.
+
+        * editing/text-iterator/range-of-string-closest-to-position-expected.txt: Added.
+        * editing/text-iterator/range-of-string-closest-to-position.html: Added.
+
 2017-01-29  Yoav Weiss  <y...@yoav.ws>
 
         Add invalid value tests to Link header handling.

Added: trunk/LayoutTests/editing/text-iterator/range-of-string-closest-to-position-expected.txt (0 => 211356)


--- trunk/LayoutTests/editing/text-iterator/range-of-string-closest-to-position-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/text-iterator/range-of-string-closest-to-position-expected.txt	2017-01-30 05:03:11 UTC (rev 211356)
@@ -0,0 +1,11 @@
+PASS internals.rangeOfStringNearLocation(rangeOfNodeContent(p1), 'the', 11).toArray() is secondThe
+PASS internals.rangeOfStringNearLocation(rangeOfNodeContent(p1), 'the', 9).toArray() is secondThe
+PASS internals.rangeOfStringNearLocation(rangeOfNodeContent(p1), 'the', 6).toArray() is firstThe
+PASS internals.rangeOfStringNearLocation(rangeOfNodeContent(p1), 'the', 16).toArray() is secondThe
+PASS internals.rangeOfStringNearLocation(rangeOfNodeContent(p1), 'The', 11).toArray() is upperThe
+PASS internals.rangeOfStringNearLocation(rangeOfNodeContent(p2), '中文', 5).toArray() is chineseMatch
+PASS internals.rangeOfStringNearLocation(rangeOfNodeContent(p3), 'the', 5).toArray() is rtlMatch
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/editing/text-iterator/range-of-string-closest-to-position.html (0 => 211356)


--- trunk/LayoutTests/editing/text-iterator/range-of-string-closest-to-position.html	                        (rev 0)
+++ trunk/LayoutTests/editing/text-iterator/range-of-string-closest-to-position.html	2017-01-30 05:03:11 UTC (rev 211356)
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8"> 
+<script src=""
+</head>
+<body>
+<div id="test" contenteditable>
+<p id="p1">the car is the good the The world.</p>
+<p id="p2">中文字是中文字</p>
+<bdo dir="rtl" id="p3">the best</bdo>
+</div>
+<div id="console"></div>
+<script>
+
+if (!window.internals)
+    testFailed('This test requires internals object');
+else {
+    var container = document.getElementById('test');
+
+    function range(startContainer, startOffset, endContainer, endOffset) {
+        var range =  document.createRange();
+        range.setStart(startContainer, startOffset);
+        range.setEnd(endContainer, endOffset);
+        return range;
+    }
+
+    function rangeOfNodeContent(referenceNode) {
+        var range = document.createRange();
+        range.selectNodeContents(referenceNode);
+        return range;
+    }
+
+    Range.prototype.toArray = function () {
+        return [this.startContainer, this.startOffset, this.endContainer, this.endOffset];
+    }
+
+    var p1 = document.getElementById("p1");
+    var p2 = document.getElementById("p2");
+    var p3 = document.getElementById("p3");
+
+    var firstThe = [p1.firstChild, 0, p1.firstChild, 3];
+    var secondThe = [p1.firstChild, 11, p1.firstChild, 14];
+    var upperThe = [p1.firstChild, 24, p1.firstChild, 27];
+    var chineseMatch = [p2.firstChild, 4, p2.firstChild, 6];
+    var germanMatch = [p3.firstChild, 5, p3.firstChild, 6];
+    var rtlMatch = [p3.firstChild, 0, p3.firstChild, 3];
+
+    // When the target position is correct, at the beginning of the matched range.
+    shouldBe('internals.rangeOfStringNearLocation(rangeOfNodeContent(p1), \'the\', 11).toArray()', 'secondThe');
+
+    // Cases that the matched range is on either side of the target position.
+    shouldBe('internals.rangeOfStringNearLocation(rangeOfNodeContent(p1), \'the\', 9).toArray()', 'secondThe');    
+    shouldBe('internals.rangeOfStringNearLocation(rangeOfNodeContent(p1), \'the\', 6).toArray()', 'firstThe');
+
+    // In this case, target position is at the beginning of "oog", make sure it's picking the 
+    // left side "the" as the closest match.
+    shouldBe('internals.rangeOfStringNearLocation(rangeOfNodeContent(p1), \'the\', 16).toArray()', 'secondThe');
+
+    // Upper case.
+    shouldBe('internals.rangeOfStringNearLocation(rangeOfNodeContent(p1), \'The\', 11).toArray()', 'upperThe');
+
+    // CJK
+    shouldBe('internals.rangeOfStringNearLocation(rangeOfNodeContent(p2), \'中文\', 5).toArray()', 'chineseMatch');
+
+    // RtL
+    shouldBe('internals.rangeOfStringNearLocation(rangeOfNodeContent(p3), \'the\', 5).toArray()', 'rtlMatch');
+
+    container.style.display = 'none';
+}
+
+</script>
+<script src=""
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (211355 => 211356)


--- trunk/Source/WebCore/ChangeLog	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebCore/ChangeLog	2017-01-30 05:03:11 UTC (rev 211356)
@@ -1,3 +1,35 @@
+2017-01-29  Nan Wang  <n_w...@apple.com>
+
+        AX: WKContentView needs to implement UITextInput methods to make speak selection highlighting work
+        https://bugs.webkit.org/show_bug.cgi?id=166955
+
+        Reviewed by Ryosuke Niwa.
+
+        Created a new version of Range::collectSelectionRect() that returns rects for each
+        line, so that Speak Selection doesn't need to handle searching for soft line breaks.
+        Also added a variant of findPlainText to search for the closest matched range to the given position.
+
+        Test: editing/text-iterator/range-of-string-closest-to-position.html
+
+        * dom/Range.cpp:
+        (WebCore::Range::collectSelectionRectsWithoutUnionInteriorLines):
+        (WebCore::Range::collectSelectionRects):
+        * dom/Range.h:
+        * editing/TextIterator.cpp:
+        (WebCore::findPlainTextMatches):
+        (WebCore::updateSearchBuffer):
+        (WebCore::findIteratorOptions):
+        (WebCore::rangeMatches):
+        (WebCore::findClosestPlainText):
+        (WebCore::findPlainText):
+        (WebCore::findPlainTextOffset): Deleted.
+        * editing/TextIterator.h:
+        * editing/htmlediting.h:
+        * testing/Internals.cpp:
+        (WebCore::Internals::rangeOfStringNearLocation):
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2017-01-29  Andy Estes  <aes...@apple.com>
 
         [QuickLook] Add a WebPreference to enable saving QuickLook documents in WebKitLegacy

Modified: trunk/Source/WebCore/dom/Range.cpp (211355 => 211356)


--- trunk/Source/WebCore/dom/Range.cpp	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebCore/dom/Range.cpp	2017-01-30 05:03:11 UTC (rev 211356)
@@ -1266,7 +1266,7 @@
 
 // This function is similar in spirit to addLineBoxRects, but annotates the returned rectangles
 // with additional state which helps iOS draw selections in its unique way.
-void Range::collectSelectionRects(Vector<SelectionRect>& rects)
+int Range::collectSelectionRectsWithoutUnionInteriorLines(Vector<SelectionRect>& rects)
 {
     auto& startContainer = this->startContainer();
     auto& endContainer = this->endContainer();
@@ -1325,7 +1325,7 @@
         VisiblePosition endPosition(createLegacyEditingPosition(&endContainer, endOffset), VP_DEFAULT_AFFINITY);
         VisiblePosition brPosition(createLegacyEditingPosition(stopNode, 0), VP_DEFAULT_AFFINITY);
         if (endPosition == brPosition)
-            rects.last().setIsLineBreak(true);    
+            rects.last().setIsLineBreak(true);
     }
 
     int lineTop = std::numeric_limits<int>::max();
@@ -1422,7 +1422,15 @@
         } else if (selectionRect.direction() == LTR && selectionRect.isLastOnLine())
             selectionRect.setLogicalWidth(selectionRect.maxX() - selectionRect.logicalLeft());
     }
+    
+    return maxLineNumber;
+}
 
+void Range::collectSelectionRects(Vector<SelectionRect>& rects)
+{
+    int maxLineNumber = collectSelectionRectsWithoutUnionInteriorLines(rects);
+    const size_t numberOfRects = rects.size();
+    
     // Union all the rectangles on interior lines (i.e. not first or last).
     // On first and last lines, just avoid having overlaps by merging intersecting rectangles.
     Vector<SelectionRect> unionedRects;

Modified: trunk/Source/WebCore/dom/Range.h (211355 => 211356)


--- trunk/Source/WebCore/dom/Range.h	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebCore/dom/Range.h	2017-01-30 05:03:11 UTC (rev 211356)
@@ -125,6 +125,7 @@
     WEBCORE_EXPORT FloatRect absoluteBoundingRect() const;
 #if PLATFORM(IOS)
     WEBCORE_EXPORT void collectSelectionRects(Vector<SelectionRect>&);
+    WEBCORE_EXPORT int collectSelectionRectsWithoutUnionInteriorLines(Vector<SelectionRect>&);
 #endif
 
     void nodeChildrenChanged(ContainerNode&);

Modified: trunk/Source/WebCore/editing/TextIterator.cpp (211355 => 211356)


--- trunk/Source/WebCore/editing/TextIterator.cpp	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebCore/editing/TextIterator.cpp	2017-01-30 05:03:11 UTC (rev 211356)
@@ -2656,10 +2656,28 @@
     return result;
 }
 
-static std::optional<std::pair<size_t, size_t>> findPlainTextOffset(SearchBuffer& buffer, CharacterIterator& findIterator, bool searchForward)
+static TextIteratorBehavior findIteratorOptions(FindOptions options)
 {
-    size_t matchStart = 0;
-    size_t matchLength = 0;
+    TextIteratorBehavior iteratorOptions = TextIteratorEntersTextControls | TextIteratorClipsToFrameAncestors;
+    if (!(options & DoNotTraverseFlatTree))
+        iteratorOptions |= TextIteratorTraversesFlatTree;
+    return iteratorOptions;
+}
+
+static void findPlainTextMatches(const Range& range, const String& target, FindOptions options, const std::function<bool(size_t, size_t)>& match)
+{
+    SearchBuffer buffer(target, options);
+    if (buffer.needsMoreContext()) {
+        Ref<Range> beforeStartRange = range.ownerDocument().createRange();
+        beforeStartRange->setEnd(range.startContainer(), range.startOffset());
+        for (SimplifiedBackwardsTextIterator backwardsIterator(beforeStartRange.get()); !backwardsIterator.atEnd(); backwardsIterator.advance()) {
+            buffer.prependContext(backwardsIterator.text());
+            if (!buffer.needsMoreContext())
+                break;
+        }
+    }
+
+    CharacterIterator findIterator(range, findIteratorOptions(options));
     while (!findIterator.atEnd()) {
         findIterator.advance(buffer.append(findIterator.text()));
         while (1) {
@@ -2674,45 +2692,53 @@
             }
             size_t lastCharacterInBufferOffset = findIterator.characterOffset();
             ASSERT(lastCharacterInBufferOffset >= matchStartOffset);
-            matchStart = lastCharacterInBufferOffset - matchStartOffset;
-            matchLength = newMatchLength;
-            if (searchForward) // Look for the last match when searching backwards instead.
-                return std::pair<size_t, size_t> { matchStart, matchLength };
+            if (match(lastCharacterInBufferOffset - matchStartOffset, newMatchLength))
+                return;
         }
     }
+}
 
+static Ref<Range> rangeForMatch(const Range& range, FindOptions options, size_t matchStart, size_t matchLength, bool searchForward)
+{
     if (!matchLength)
-        return std::nullopt;
+        return collapsedToBoundary(range, searchForward);
+    CharacterIterator rangeComputeIterator(range, findIteratorOptions(options));
+    return characterSubrange(range.ownerDocument(), rangeComputeIterator, matchStart, matchLength);
+}
 
-    return std::pair<size_t, size_t> { matchStart, matchLength };
+Ref<Range> findClosestPlainText(const Range& range, const String& target, FindOptions options, unsigned targetOffset)
+{
+    size_t matchStart = 0;
+    size_t matchLength = 0;
+    size_t distance = std::numeric_limits<size_t>::max();
+    auto match = [targetOffset, &distance, &matchStart, &matchLength] (size_t start, size_t length) {
+        size_t newDistance = std::min(abs(static_cast<signed>(start - targetOffset)), abs(static_cast<signed>(start + length - targetOffset)));
+        if (newDistance < distance) {
+            matchStart = start;
+            matchLength = length;
+            distance = newDistance;
+        }
+        return false;
+    };
+
+    findPlainTextMatches(range, target, options, match);
+    return rangeForMatch(range, options, matchStart, matchLength, !(options & Backwards));
 }
 
 Ref<Range> findPlainText(const Range& range, const String& target, FindOptions options)
 {
-    SearchBuffer buffer(target, options);
-
-    if (buffer.needsMoreContext()) {
-        Ref<Range> beforeStartRange = range.ownerDocument().createRange();
-        beforeStartRange->setEnd(range.startContainer(), range.startOffset());
-        for (SimplifiedBackwardsTextIterator backwardsIterator(beforeStartRange.get()); !backwardsIterator.atEnd(); backwardsIterator.advance()) {
-            buffer.prependContext(backwardsIterator.text());
-            if (!buffer.needsMoreContext())
-                break;
-        }
-    }
-
     bool searchForward = !(options & Backwards);
-    TextIteratorBehavior iteratorOptions = TextIteratorEntersTextControls | TextIteratorClipsToFrameAncestors;
-    if (!(options & DoNotTraverseFlatTree))
-        iteratorOptions |= TextIteratorTraversesFlatTree;
+    size_t matchStart = 0;
+    size_t matchLength = 0;
+    auto match = [searchForward, &matchStart, &matchLength] (size_t start, size_t length) {
+        matchStart = start;
+        matchLength = length;
+        // Look for the last match when searching backwards instead.
+        return searchForward;
+    };
 
-    CharacterIterator findIterator(range, iteratorOptions);
-    auto result = findPlainTextOffset(buffer, findIterator, searchForward);
-    if (!result)
-        return collapsedToBoundary(range, searchForward);
-
-    CharacterIterator rangeComputeIterator(range, iteratorOptions);
-    return characterSubrange(range.ownerDocument(), rangeComputeIterator, result->first, result->second);
+    findPlainTextMatches(range, target, options, match);
+    return rangeForMatch(range, options, matchStart, matchLength, searchForward);
 }
 
 }

Modified: trunk/Source/WebCore/editing/TextIterator.h (211355 => 211356)


--- trunk/Source/WebCore/editing/TextIterator.h	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebCore/editing/TextIterator.h	2017-01-30 05:03:11 UTC (rev 211356)
@@ -45,6 +45,7 @@
 WEBCORE_EXPORT String plainText(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior, bool isDisplayString = false);
 WEBCORE_EXPORT String plainTextReplacingNoBreakSpace(const Range*, TextIteratorBehavior = TextIteratorDefaultBehavior, bool isDisplayString = false);
 Ref<Range> findPlainText(const Range&, const String&, FindOptions);
+WEBCORE_EXPORT Ref<Range> findClosestPlainText(const Range&, const String&, FindOptions, unsigned);
 
 // FIXME: Move this somewhere else in the editing directory. It doesn't belong here.
 bool isRendererReplacedElement(RenderObject*);

Modified: trunk/Source/WebCore/editing/htmlediting.h (211355 => 211356)


--- trunk/Source/WebCore/editing/htmlediting.h	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebCore/editing/htmlediting.h	2017-01-30 05:03:11 UTC (rev 211356)
@@ -144,9 +144,9 @@
 
 int comparePositions(const VisiblePosition&, const VisiblePosition&);
 
-int indexForVisiblePosition(const VisiblePosition&, RefPtr<ContainerNode>& scope);
+WEBCORE_EXPORT int indexForVisiblePosition(const VisiblePosition&, RefPtr<ContainerNode>& scope);
 int indexForVisiblePosition(Node&, const VisiblePosition&, bool forSelectionPreservation);
-VisiblePosition visiblePositionForIndex(int index, ContainerNode* scope);
+WEBCORE_EXPORT VisiblePosition visiblePositionForIndex(int index, ContainerNode* scope);
 VisiblePosition visiblePositionForIndexUsingCharacterIterator(Node&, int index); // FIXME: Why do we need this version?
 
 // -------------------------------------------------------------------------

Modified: trunk/Source/WebCore/testing/Internals.cpp (211355 => 211356)


--- trunk/Source/WebCore/testing/Internals.cpp	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebCore/testing/Internals.cpp	2017-01-30 05:03:11 UTC (rev 211356)
@@ -1456,6 +1456,11 @@
     return TextIterator::subrange(&range, rangeLocation, rangeLength);
 }
 
+RefPtr<Range> Internals::rangeOfStringNearLocation(const Range& searchRange, const String& text, unsigned targetOffset)
+{
+    return findClosestPlainText(searchRange, text, 0, targetOffset);
+}
+
 ExceptionOr<RefPtr<Range>> Internals::rangeForDictionaryLookupAtLocation(int x, int y)
 {
 #if PLATFORM(MAC)

Modified: trunk/Source/WebCore/testing/Internals.h (211355 => 211356)


--- trunk/Source/WebCore/testing/Internals.h	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebCore/testing/Internals.h	2017-01-30 05:03:11 UTC (rev 211356)
@@ -201,6 +201,7 @@
     String rangeAsText(const Range&);
     Ref<Range> subrange(Range&, int rangeLocation, int rangeLength);
     ExceptionOr<RefPtr<Range>> rangeForDictionaryLookupAtLocation(int x, int y);
+    RefPtr<Range> rangeOfStringNearLocation(const Range&, const String&, unsigned);
 
     ExceptionOr<void> setDelegatesScrolling(bool enabled);
 

Modified: trunk/Source/WebCore/testing/Internals.idl (211355 => 211356)


--- trunk/Source/WebCore/testing/Internals.idl	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebCore/testing/Internals.idl	2017-01-30 05:03:11 UTC (rev 211356)
@@ -195,6 +195,7 @@
     DOMString rangeAsText(Range range);
     Range subrange(Range range, long rangeLocation, long rangeLength);
     [MayThrowException] Range? rangeForDictionaryLookupAtLocation(long x, long y);
+    Range? rangeOfStringNearLocation(Range range, DOMString text, long targetOffset);
 
     [MayThrowException] void setDelegatesScrolling(boolean enabled);
 

Modified: trunk/Source/WebKit2/ChangeLog (211355 => 211356)


--- trunk/Source/WebKit2/ChangeLog	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebKit2/ChangeLog	2017-01-30 05:03:11 UTC (rev 211356)
@@ -1,3 +1,34 @@
+2017-01-29  Nan Wang  <n_w...@apple.com>
+
+        AX: WKContentView needs to implement UITextInput methods to make speak selection highlighting work
+        https://bugs.webkit.org/show_bug.cgi?id=166955
+
+        Reviewed by Ryosuke Niwa.
+
+        Implemented methods that Speak Selection can use to retrieve the word/sentence highlighting rects.
+
+        * Scripts/webkit/messages.py:
+        (headers_for_type):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/WebPageProxy.messages.in:
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView webSelectionRectsForSelectionRects:]):
+        (-[WKContentView webSelectionRects]):
+        (-[WKContentView _accessibilityRetrieveRectsEnclosingSelectionOffset:withGranularity:]):
+        (-[WKContentView _accessibilityRetrieveRectsAtSelectionOffset:withText:]):
+        * UIProcess/ios/WebPageProxyIOS.mm:
+        (WebKit::WebPageProxy::selectionRectsCallback):
+        (WebKit::WebPageProxy::requestRectsForGranularityWithSelectionOffset):
+        (WebKit::WebPageProxy::requestRectsAtSelectionOffsetWithText):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::visiblePositionForPositionWithOffset):
+        (WebKit::WebPage::getRectsForGranularityWithSelectionOffset):
+        (WebKit::rangeNearPositionMatchesText):
+        (WebKit::WebPage::getRectsAtSelectionOffsetWithText):
+
 2017-01-29  Dan Bernstein  <m...@apple.com>
 
         [iOS] Expose WebCore::DataDetection::detectContentInRange WKWebProcessPlugInRangeHandle

Modified: trunk/Source/WebKit2/Scripts/webkit/messages.py (211355 => 211356)


--- trunk/Source/WebKit2/Scripts/webkit/messages.py	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebKit2/Scripts/webkit/messages.py	2017-01-30 05:03:11 UTC (rev 211356)
@@ -365,6 +365,7 @@
         'WebCore::TextIndicatorData': ['<WebCore/TextIndicator.h>'],
         'WebCore::TextureMapperAnimations': ['<WebCore/TextureMapperAnimation.h>'],
         'WebCore::ViewportAttributes': ['<WebCore/ViewportArguments.h>'],
+        'WebCore::SelectionRect': ['"EditorState.h"'],
         'WebKit::BackForwardListItemState': ['"SessionState.h"'],
         'WebKit::LayerHostingMode': ['"LayerTreeContext.h"'],
         'WebKit::PageState': ['"SessionState.h"'],

Modified: trunk/Source/WebKit2/UIProcess/WebPageProxy.h (211355 => 211356)


--- trunk/Source/WebKit2/UIProcess/WebPageProxy.h	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebKit2/UIProcess/WebPageProxy.h	2017-01-30 05:03:11 UTC (rev 211356)
@@ -256,6 +256,7 @@
 #if PLATFORM(IOS)
 typedef GenericCallback<const WebCore::IntPoint&, uint32_t, uint32_t, uint32_t> GestureCallback;
 typedef GenericCallback<const WebCore::IntPoint&, uint32_t, uint32_t> TouchesCallback;
+typedef GenericCallback<const Vector<WebCore::SelectionRect>&> SelectionRectsCallback;
 struct NodeAssistanceArguments {
     AssistedNodeInformation m_nodeInformation;
     bool m_userIsInteracting;
@@ -545,6 +546,8 @@
     void handleTwoFingerTapAtPoint(const WebCore::IntPoint&, uint64_t requestID);
     void setForceAlwaysUserScalable(bool);
     void setIsScrollingOrZooming(bool);
+    void requestRectsForGranularityWithSelectionOffset(WebCore::TextGranularity, uint32_t offset, std::function<void(const Vector<WebCore::SelectionRect>&, CallbackBase::Error)>);
+    void requestRectsAtSelectionOffsetWithText(int32_t offset, const String&, std::function<void(const Vector<WebCore::SelectionRect>&, CallbackBase::Error)>);
 #if ENABLE(DATA_INTERACTION)
     void didPerformDataInteractionControllerOperation();
     void requestStartDataInteraction(const WebCore::IntPoint& clientPosition, const WebCore::IntPoint& globalPosition);
@@ -1477,6 +1480,7 @@
     void selectionContextCallback(const String&, const String&, const String&, uint64_t);
     void interpretKeyEvent(const EditorState&, bool isCharEvent, bool& handled);
     void showPlaybackTargetPicker(bool hasVideo, const WebCore::IntRect& elementRect);
+    void selectionRectsCallback(const Vector<WebCore::SelectionRect>&, uint64_t);
 #endif
 #if PLATFORM(GTK)
     void printFinishedCallback(const WebCore::ResourceError&, uint64_t);

Modified: trunk/Source/WebKit2/UIProcess/WebPageProxy.messages.in (211355 => 211356)


--- trunk/Source/WebKit2/UIProcess/WebPageProxy.messages.in	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebKit2/UIProcess/WebPageProxy.messages.in	2017-01-30 05:03:11 UTC (rev 211356)
@@ -195,6 +195,7 @@
     DidNotHandleTapAsClick(WebCore::IntPoint point)
     DisableDoubleTapGesturesDuringTapIfNecessary(uint64_t requestID)
     DrawToPDFCallback(IPC::DataReference pdfData, uint64_t callbackID)
+    SelectionRectsCallback(Vector<WebCore::SelectionRect> selectionRects, uint64_t callbackID);
 #endif
 #if ENABLE(DATA_DETECTION)
     SetDataDetectionResult(struct WebKit::DataDetectionResult dataDetectionResult)

Modified: trunk/Source/WebKit2/UIProcess/ios/WKContentViewInteraction.h (211355 => 211356)


--- trunk/Source/WebKit2/UIProcess/ios/WKContentViewInteraction.h	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebKit2/UIProcess/ios/WKContentViewInteraction.h	2017-01-30 05:03:11 UTC (rev 211356)
@@ -247,6 +247,8 @@
 - (NSArray *)_dataDetectionResults;
 - (NSArray<NSValue *> *)_uiTextSelectionRects;
 - (void)accessibilityRetrieveSpeakSelectionContent;
+- (void)_accessibilityRetrieveRectsEnclosingSelectionOffset:(NSInteger)offset withGranularity:(UITextGranularity)granularity;
+- (void)_accessibilityRetrieveRectsAtSelectionOffset:(NSInteger)offset withText:(NSString *)text;
 
 #if ENABLE(DATA_INTERACTION)
 - (void)_didPerformDataInteractionControllerOperation;

Modified: trunk/Source/WebKit2/UIProcess/ios/WKContentViewInteraction.mm (211355 => 211356)


--- trunk/Source/WebKit2/UIProcess/ios/WKContentViewInteraction.mm	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebKit2/UIProcess/ios/WKContentViewInteraction.mm	2017-01-30 05:03:11 UTC (rev 211356)
@@ -495,6 +495,8 @@
 
 @interface WKContentView (WKInteractionPrivate)
 - (void)accessibilitySpeakSelectionSetContent:(NSString *)string;
+- (NSArray *)webSelectionRectsForSelectionRects:(const Vector<WebCore::SelectionRect>&)selectionRects;
+- (void)_accessibilityDidGetSelectionRects:(NSArray *)selectionRects withGranularity:(UITextGranularity)granularity atOffset:(NSInteger)offset;
 @end
 
 @implementation WKContentView (WKInteraction)
@@ -1496,11 +1498,8 @@
     return _positionInformation.nodeAtPositionIsAssistedNode;
 }
 
-- (NSArray *)webSelectionRects
+- (NSArray *)webSelectionRectsForSelectionRects:(const Vector<WebCore::SelectionRect>&)selectionRects
 {
-    if (_page->editorState().selectionIsNone)
-        return nil;
-    const auto& selectionRects = _page->editorState().postLayoutData().selectionRects;
     unsigned size = selectionRects.size();
     if (!size)
         return nil;
@@ -1524,6 +1523,14 @@
     return webRects;
 }
 
+- (NSArray *)webSelectionRects
+{
+    if (_page->editorState().selectionIsNone)
+        return nil;
+    const auto& selectionRects = _page->editorState().postLayoutData().selectionRects;
+    return [self webSelectionRectsForSelectionRects:selectionRects];
+}
+
 - (void)_highlightLongPressRecognized:(UILongPressGestureRecognizer *)gestureRecognizer
 {
     ASSERT(gestureRecognizer == _highlightLongPressGestureRecognizer);
@@ -2197,6 +2204,28 @@
     });
 }
 
+- (void)_accessibilityRetrieveRectsEnclosingSelectionOffset:(NSInteger)offset withGranularity:(UITextGranularity)granularity
+{
+    RetainPtr<WKContentView> view = self;
+    _page->requestRectsForGranularityWithSelectionOffset(toWKTextGranularity(granularity), offset , [view, offset, granularity](const Vector<WebCore::SelectionRect>& selectionRects, CallbackBase::Error error) {
+        if (error != WebKit::CallbackBase::Error::None)
+            return;
+        if ([view respondsToSelector:@selector(_accessibilityDidGetSelectionRects:withGranularity:atOffset:)])
+            [view _accessibilityDidGetSelectionRects:[view webSelectionRectsForSelectionRects:selectionRects] withGranularity:granularity atOffset:offset];
+    });
+}
+
+- (void)_accessibilityRetrieveRectsAtSelectionOffset:(NSInteger)offset withText:(NSString *)text
+{
+    RetainPtr<WKContentView> view = self;
+    _page->requestRectsAtSelectionOffsetWithText(offset, text, [view, offset](const Vector<WebCore::SelectionRect>& selectionRects, CallbackBase::Error error) {
+        if (error != WebKit::CallbackBase::Error::None)
+            return;
+        if ([view respondsToSelector:@selector(_accessibilityDidGetSelectionRects:withGranularity:atOffset:)])
+            [view _accessibilityDidGetSelectionRects:[view webSelectionRectsForSelectionRects:selectionRects] withGranularity:UITextGranularityWord atOffset:offset];
+    });
+}
+
 // UIWKInteractionViewProtocol
 
 static inline GestureType toGestureType(UIWKGestureType gestureType)

Modified: trunk/Source/WebKit2/UIProcess/ios/WebPageProxyIOS.mm (211355 => 211356)


--- trunk/Source/WebKit2/UIProcess/ios/WebPageProxyIOS.mm	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebKit2/UIProcess/ios/WebPageProxyIOS.mm	2017-01-30 05:03:11 UTC (rev 211356)
@@ -186,6 +186,17 @@
     callback->performCallbackWithReturnValue(beforeText, markedText, selectedText, afterText, location, length);
 }
 
+void WebPageProxy::selectionRectsCallback(const Vector<WebCore::SelectionRect>& selectionRects, uint64_t callbackID)
+{
+    auto callback = m_callbacks.take<SelectionRectsCallback>(callbackID);
+    if (!callback) {
+        ASSERT_NOT_REACHED();
+        return;
+    }
+    
+    callback->performCallbackWithReturnValue(selectionRects);
+}
+
 void WebPageProxy::updateVisibleContentRects(const VisibleContentRectUpdateInfo& visibleContentRectUpdate)
 {
     if (!isValid())
@@ -722,6 +733,28 @@
     m_process->send(Messages::WebPage::SelectWordBackward(), m_pageID);
 }
 
+void WebPageProxy::requestRectsForGranularityWithSelectionOffset(WebCore::TextGranularity granularity, uint32_t offset, std::function<void(const Vector<WebCore::SelectionRect>&, CallbackBase::Error)> callbackFunction)
+{
+    if (!isValid()) {
+        callbackFunction(Vector<WebCore::SelectionRect>(), CallbackBase::Error::Unknown);
+        return;
+    }
+    
+    uint64_t callbackID = m_callbacks.put(WTFMove(callbackFunction), m_process->throttler().backgroundActivityToken());
+    m_process->send(Messages::WebPage::GetRectsForGranularityWithSelectionOffset(static_cast<uint32_t>(granularity), offset, callbackID), m_pageID);
+}
+
+void WebPageProxy::requestRectsAtSelectionOffsetWithText(int32_t offset, const String& text, std::function<void(const Vector<WebCore::SelectionRect>&, CallbackBase::Error)> callbackFunction)
+{
+    if (!isValid()) {
+        callbackFunction(Vector<WebCore::SelectionRect>(), CallbackBase::Error::Unknown);
+        return;
+    }
+    
+    uint64_t callbackID = m_callbacks.put(WTFMove(callbackFunction), m_process->throttler().backgroundActivityToken());
+    m_process->send(Messages::WebPage::GetRectsAtSelectionOffsetWithText(offset, text, callbackID), m_pageID);
+}
+
 void WebPageProxy::moveSelectionByOffset(int32_t offset, std::function<void (CallbackBase::Error)> callbackFunction)
 {
     if (!isValid()) {

Modified: trunk/Source/WebKit2/WebProcess/WebPage/WebPage.h (211355 => 211356)


--- trunk/Source/WebKit2/WebProcess/WebPage/WebPage.h	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebKit2/WebProcess/WebPage/WebPage.h	2017-01-30 05:03:11 UTC (rev 211356)
@@ -577,6 +577,8 @@
     void updateSelectionAppearance();
     void getSelectionContext(uint64_t callbackID);
     void handleTwoFingerTapAtPoint(const WebCore::IntPoint&, uint64_t requestID);
+    void getRectsForGranularityWithSelectionOffset(uint32_t, int32_t, uint64_t callbackID);
+    void getRectsAtSelectionOffsetWithText(int32_t, const String&, uint64_t callbackID);
 #if ENABLE(IOS_TOUCH_EVENTS)
     void dispatchAsynchronousTouchEvents(const Vector<WebTouchEvent, 1>& queue);
 #endif

Modified: trunk/Source/WebKit2/WebProcess/WebPage/WebPage.messages.in (211355 => 211356)


--- trunk/Source/WebKit2/WebProcess/WebPage/WebPage.messages.in	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebKit2/WebProcess/WebPage/WebPage.messages.in	2017-01-30 05:03:11 UTC (rev 211356)
@@ -96,6 +96,8 @@
     SetAllowsMediaDocumentInlinePlayback(bool allows)
     HandleTwoFingerTapAtPoint(WebCore::IntPoint point, uint64_t requestID)
     SetForceAlwaysUserScalable(bool userScalable)
+    GetRectsForGranularityWithSelectionOffset(uint32_t granularity, int32_t offset, uint64_t callbackID);
+    GetRectsAtSelectionOffsetWithText(int32_t offset, String text, uint64_t callbackID);
 #endif
 
     SetControlledByAutomation(bool controlled)

Modified: trunk/Source/WebKit2/WebProcess/WebPage/ios/WebPageIOS.mm (211355 => 211356)


--- trunk/Source/WebKit2/WebProcess/WebPage/ios/WebPageIOS.mm	2017-01-30 01:40:29 UTC (rev 211355)
+++ trunk/Source/WebKit2/WebProcess/WebPage/ios/WebPageIOS.mm	2017-01-30 05:03:11 UTC (rev 211356)
@@ -105,6 +105,7 @@
 #import <WebCore/VisibleUnits.h>
 #import <WebCore/WKContentObservation.h>
 #import <WebCore/WebEvent.h>
+#import <WebCore/htmlediting.h>
 #import <wtf/MathExtras.h>
 #import <wtf/SetForScope.h>
 
@@ -1819,6 +1820,84 @@
     send(Messages::WebPageProxy::VoidCallback(callbackID));
 }
 
+static VisiblePosition visiblePositionForPositionWithOffset(const VisiblePosition& position, int32_t offset)
+{
+    RefPtr<ContainerNode> root;
+    unsigned startIndex = indexForVisiblePosition(position, root);
+    return visiblePositionForIndex(startIndex + offset, root.get());
+}
+
+void WebPage::getRectsForGranularityWithSelectionOffset(uint32_t granularity, int32_t offset, uint64_t callbackID)
+{
+    Frame& frame = m_page->focusController().focusedOrMainFrame();
+    VisibleSelection selection = frame.selection().selection();
+    VisiblePosition selectionStart = selection.visibleStart();
+
+    if (selectionStart.isNull()) {
+        send(Messages::WebPageProxy::SelectionRectsCallback({ }, callbackID));
+        return;
+    }
+
+    auto position = visiblePositionForPositionWithOffset(selectionStart, offset);
+    SelectionDirection direction = offset < 0 ? DirectionBackward : DirectionForward;
+
+    auto range = enclosingTextUnitOfGranularity(position, static_cast<WebCore::TextGranularity>(granularity), direction);
+    if (!range || range->collapsed()) {
+        send(Messages::WebPageProxy::SelectionRectsCallback({ }, callbackID));
+        return;
+    }
+
+    Vector<WebCore::SelectionRect> selectionRects;
+    range->collectSelectionRectsWithoutUnionInteriorLines(selectionRects);
+    convertSelectionRectsToRootView(frame.view(), selectionRects);
+    send(Messages::WebPageProxy::SelectionRectsCallback(selectionRects, callbackID));
+}
+
+static RefPtr<Range> rangeNearPositionMatchesText(const VisiblePosition& position, RefPtr<Range> originalRange, const String& matchText, RefPtr<Range> selectionRange)
+{
+    auto range = Range::create(selectionRange->ownerDocument(), selectionRange->startPosition(), position.deepEquivalent().parentAnchoredEquivalent());
+    unsigned targetOffset = TextIterator::rangeLength(range.ptr(), true);
+    return findClosestPlainText(*selectionRange.get(), matchText, 0, targetOffset);
+}
+
+void WebPage::getRectsAtSelectionOffsetWithText(int32_t offset, const String& text, uint64_t callbackID)
+{
+    Frame& frame = m_page->focusController().focusedOrMainFrame();
+    uint32_t length = text.length();
+    VisibleSelection selection = frame.selection().selection();
+    VisiblePosition selectionStart = selection.visibleStart();
+    VisiblePosition selectionEnd = selection.visibleEnd();
+
+    if (selectionStart.isNull() || selectionEnd.isNull()) {
+        send(Messages::WebPageProxy::SelectionRectsCallback({ }, callbackID));
+        return;
+    }
+
+    auto startPosition = visiblePositionForPositionWithOffset(selectionStart, offset);
+    auto endPosition = visiblePositionForPositionWithOffset(startPosition, length);
+    auto range = Range::create(*frame.document(), startPosition, endPosition);
+
+    if (range->collapsed()) {
+        send(Messages::WebPageProxy::SelectionRectsCallback({ }, callbackID));
+        return;
+    }
+
+    String rangeText = plainTextReplacingNoBreakSpace(range.ptr(), TextIteratorDefaultBehavior, true);
+    if (rangeText != text) {
+        auto selectionRange = selection.toNormalizedRange();
+        // Try to search for a range which is the closest to the position within the selection range that matches the passed in text.
+        if (auto wordRange = rangeNearPositionMatchesText(startPosition, range.ptr(), text, selectionRange)) {
+            if (!wordRange->collapsed())
+                range = *wordRange;
+        }
+    }
+
+    Vector<WebCore::SelectionRect> selectionRects;
+    range->collectSelectionRectsWithoutUnionInteriorLines(selectionRects);
+    convertSelectionRectsToRootView(frame.view(), selectionRects);
+    send(Messages::WebPageProxy::SelectionRectsCallback(selectionRects, callbackID));
+}
+
 VisiblePosition WebPage::visiblePositionInFocusedNodeForPoint(const Frame& frame, const IntPoint& point, bool isInteractingWithAssistedNode)
 {
     IntPoint adjustedPoint(frame.view()->rootViewToContents(point));
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to