Add a mechanism to find and manipulate text by paragraphs
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 25 Oct 2019 00:08:13 +0000 (00:08 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 25 Oct 2019 00:08:13 +0000 (00:08 +0000)
https://bugs.webkit.org/show_bug.cgi?id=203286

Reviewed by Wenson Hsieh.

Source/WebCore:

This patch introduces TextManipulationController which allows WebKit clients to find and replace
text content in a given document by paragraph boundary. For now, TextManipulationController finds
all paragraphs in the document and invokes the callback for each paragraph. In the future, this
controller will support detecting removal and insertion of new content to the document.

Tests: TextManipulation.StartTextManipulationFindSimpleParagraphs
       TextManipulation.StartTextManipulationFindParagraphsWithMultileTokens
       TextManipulation.CompleteTextManipulationShouldReplaceSimpleParagraphContent
       TextManipulation.CompleteTextManipulationShouldFailWhenContentIsChanged
       TextManipulation.CompleteTextManipulationShouldFailWhenDocumentHasBeenNavigatedAway

* Headers.cmake:
* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:
* dom/Document.cpp:
(WebCore::Document::prepareForDestruction): Clear TextManipulationController. Otherwise m_items
in TextManipulationController may keep a bunch of nodes alive and not let this document destructed.
(WebCore::Document::textManipulationController): Added.
* dom/Document.h:
(WebCore::Document::textManipulationControllerIfExists): Added.
* editing/TextManipulationController.cpp: Added.
(WebCore::TextManipulationController::TextManipulationController): Added.
(WebCore::TextManipulationController::startObservingParagraphs): Finds all pargraphs and invoke
the callback.
(WebCore::TextManipulationController::addItem): Added. A helper for startObservingParagraphs.
(WebCore::TextManipulationController::completeManipulation): Added.
(WebCore::TextManipulationController::replace): Added. A helper for completeManipulation.
* editing/TextManipulationController.h: Added.
(WebCore::TextManipulationController::ManipulationToken::encode const): Added.
(WebCore::TextManipulationController::ManipulationToken::decode): Added.

Source/WebKit:

This patch introduces a new SPI to find paragraphs of text and let client replace the content of each paragraph.
For now, this SPI is limited to find & replace contents of main frame's document.

WKWebView's _startTextManipulationsWithCompletionHandler sends StartTextManipulations message to WebContent process
to find all paragraphs in the main frame. WebContent process will send back DidFindTextManipulationItem message
for each paragraph, which in turn calls back _WKTextManipulationDelegate's _webView: didFindTextManipulationItem:.

Upon receiving this delegate callback, the client can invoke WKWebView's _completeTextManipulation to replace
the content. It will send CompleteTextManipulation to WebContent process, which will invoke completeManipulation
on main frame's document's TextManipulationController.

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _textManipulationDelegate]): Aded.
(-[WKWebView _setTextManipulationDelegate:]): Aded.
(-[WKWebView _startTextManipulationsWithCompletionHandler:]):
(-[WKWebView _completeTextManipulation:completion:]):
* UIProcess/API/Cocoa/WKWebViewPrivate.h:
* UIProcess/API/Cocoa/_WKTextManipulationDelegate.h: Added.
* UIProcess/API/Cocoa/_WKTextManipulationItem.h: Added.
* UIProcess/API/Cocoa/_WKTextManipulationItem.mm: Added.
(-[_WKTextManipulationItem initWithIdentifier:tokens:]):
(-[_WKTextManipulationItem identifier]):
(-[_WKTextManipulationItem tokens]):
* UIProcess/API/Cocoa/_WKTextManipulationToken.h: Added.
* UIProcess/API/Cocoa/_WKTextManipulationToken.mm: Added.
(-[_WKTextManipulationToken init]):
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::startTextManipulations):
(WebKit::WebPageProxy::didFindTextManipulationItem):
(WebKit::WebPageProxy::completeTextManipulation):
* UIProcess/WebPageProxy.h:
* UIProcess/WebPageProxy.messages.in:
* WebKit.xcodeproj/project.pbxproj:
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::startTextManipulations):
(WebKit::WebPage::completeTextManipulation):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:

Tools:

Added basic API tests for the new SPI.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm: Added.
(-[TextManipulationDelegate init]):
(-[TextManipulationDelegate _webView:didFindItem:forFrame:]):
(-[TextManipulationDelegate items]):
(TestWebKitAPI::createItem):

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

31 files changed:
Source/WebCore/ChangeLog
Source/WebCore/Headers.cmake
Source/WebCore/Sources.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/editing/TextManipulationController.cpp [new file with mode: 0644]
Source/WebCore/editing/TextManipulationController.h [new file with mode: 0644]
Source/WebKit/ChangeLog
Source/WebKit/SourcesCocoa.txt
Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h
Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationDelegate.h [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationItem.h [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationItem.mm [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationToken.h [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationToken.mm [new file with mode: 0644]
Source/WebKit/UIProcess/Cocoa/DownloadProxyMapCocoa.mm
Source/WebKit/UIProcess/Cocoa/SOAuthorization/SOAuthorizationSession.mm
Source/WebKit/UIProcess/Cocoa/WebProcessPoolCocoa.mm
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/WebPageProxy.messages.in
Source/WebKit/UIProcess/mac/WKFullScreenWindowController.mm
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/WebPage.messages.in
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm [new file with mode: 0644]

index 6c277f9..2641e98 100644 (file)
@@ -1,3 +1,41 @@
+2019-10-23  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Add a mechanism to find and manipulate text by paragraphs
+        https://bugs.webkit.org/show_bug.cgi?id=203286
+
+        Reviewed by Wenson Hsieh.
+
+        This patch introduces TextManipulationController which allows WebKit clients to find and replace
+        text content in a given document by paragraph boundary. For now, TextManipulationController finds
+        all paragraphs in the document and invokes the callback for each paragraph. In the future, this
+        controller will support detecting removal and insertion of new content to the document.
+
+        Tests: TextManipulation.StartTextManipulationFindSimpleParagraphs
+               TextManipulation.StartTextManipulationFindParagraphsWithMultileTokens
+               TextManipulation.CompleteTextManipulationShouldReplaceSimpleParagraphContent
+               TextManipulation.CompleteTextManipulationShouldFailWhenContentIsChanged
+               TextManipulation.CompleteTextManipulationShouldFailWhenDocumentHasBeenNavigatedAway
+
+        * Headers.cmake:
+        * Sources.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * dom/Document.cpp:
+        (WebCore::Document::prepareForDestruction): Clear TextManipulationController. Otherwise m_items
+        in TextManipulationController may keep a bunch of nodes alive and not let this document destructed.
+        (WebCore::Document::textManipulationController): Added.
+        * dom/Document.h:
+        (WebCore::Document::textManipulationControllerIfExists): Added.
+        * editing/TextManipulationController.cpp: Added.
+        (WebCore::TextManipulationController::TextManipulationController): Added.
+        (WebCore::TextManipulationController::startObservingParagraphs): Finds all pargraphs and invoke
+        the callback.
+        (WebCore::TextManipulationController::addItem): Added. A helper for startObservingParagraphs.
+        (WebCore::TextManipulationController::completeManipulation): Added.
+        (WebCore::TextManipulationController::replace): Added. A helper for completeManipulation.
+        * editing/TextManipulationController.h: Added.
+        (WebCore::TextManipulationController::ManipulationToken::encode const): Added.
+        (WebCore::TextManipulationController::ManipulationToken::decode): Added.
+
 2019-10-24  Ryosuke Niwa  <rniwa@webkit.org>
 
         Build fix after r251567.
index e79d815..d60d8f8 100644 (file)
@@ -503,6 +503,7 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS
     editing/TextGranularity.h
     editing/TextIterator.h
     editing/TextIteratorBehavior.h
+    editing/TextManipulationController.h
     editing/UndoStep.h
     editing/VisiblePosition.h
     editing/VisibleSelection.h
index edb630d..89dcf8e 100644 (file)
@@ -1061,6 +1061,7 @@ editing/SplitTextNodeContainingElementCommand.cpp
 editing/TextCheckingHelper.cpp
 editing/TextInsertionBaseCommand.cpp
 editing/TextIterator.cpp
+editing/TextManipulationController.cpp
 editing/TypingCommand.cpp
 editing/UnlinkCommand.cpp
 editing/VisiblePosition.cpp
index 39cebec..2171d65 100644 (file)
                996E59DF1DF0128D006612B9 /* NavigatorWebDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 996E59DC1DF00D90006612B9 /* NavigatorWebDriver.h */; };
                9A528E8417D7F52F00AA9518 /* FloatingObjects.h in Headers */ = {isa = PBXBuildFile; fileRef = 9A528E8217D7F52F00AA9518 /* FloatingObjects.h */; settings = {ATTRIBUTES = (Private, ); }; };
                9AB1F38018E2489A00534743 /* CSSToLengthConversionData.h in Headers */ = {isa = PBXBuildFile; fileRef = 9AB1F37E18E2489A00534743 /* CSSToLengthConversionData.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               9B02E0C8235EAD2A004044B2 /* TextManipulationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B02E0C3235E76AA004044B2 /* TextManipulationController.h */; settings = {ATTRIBUTES = (Private, ); }; };
                9B24DE8E15194B9500C59C27 /* HTMLBDIElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B24DE8C15194B9500C59C27 /* HTMLBDIElement.h */; };
                9B27FC60234D9ADB00394A46 /* WindowEventLoop.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B27FC5E234D9ADA00394A46 /* WindowEventLoop.h */; };
                9B2D8A7914997CCF00ECEF3E /* UndoStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B2D8A7814997CCF00ECEF3E /* UndoStep.h */; settings = {ATTRIBUTES = (Private, ); }; };
                9AC6F02521148F5400CBDA06 /* MediaEngineConfigurationFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MediaEngineConfigurationFactory.h; path = mediacapabilities/MediaEngineConfigurationFactory.h; sourceTree = "<group>"; };
                9AC6F02D21148F9F00CBDA06 /* MediaEngineConfigurationFactoryMock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MediaEngineConfigurationFactoryMock.cpp; sourceTree = "<group>"; };
                9AC6F02F21148FA200CBDA06 /* MediaEngineConfigurationFactoryMock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaEngineConfigurationFactoryMock.h; sourceTree = "<group>"; };
+               9B02E0C3235E76AA004044B2 /* TextManipulationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TextManipulationController.h; sourceTree = "<group>"; };
+               9B02E0C4235E76AA004044B2 /* TextManipulationController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TextManipulationController.cpp; sourceTree = "<group>"; };
                9B03D8061BB3110D00B73F64 /* ReadableByteStreamInternalsBuiltins.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ReadableByteStreamInternalsBuiltins.h; path = DerivedSources/WebCore/ReadableByteStreamInternalsBuiltins.h; sourceTree = BUILT_PRODUCTS_DIR; };
                9B03D8061BB3110D00B764C9 /* StreamInternalsBuiltins.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StreamInternalsBuiltins.h; path = DerivedSources/WebCore/StreamInternalsBuiltins.h; sourceTree = BUILT_PRODUCTS_DIR; };
                9B03D8061BB3110D00B764D8 /* ReadableStreamBuiltins.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ReadableStreamBuiltins.h; path = DerivedSources/WebCore/ReadableStreamBuiltins.h; sourceTree = BUILT_PRODUCTS_DIR; };
                                93309DCC099E64910056E581 /* TextIterator.cpp */,
                                93309DCD099E64910056E581 /* TextIterator.h */,
                                9392146818A6D791000EE688 /* TextIteratorBehavior.h */,
+                               9B02E0C4235E76AA004044B2 /* TextManipulationController.cpp */,
+                               9B02E0C3235E76AA004044B2 /* TextManipulationController.h */,
                                93309DCA099E64910056E581 /* TypingCommand.cpp */,
                                93309DCB099E64910056E581 /* TypingCommand.h */,
                                9B2D8A7814997CCF00ECEF3E /* UndoStep.h */,
                                759CB837192DA9190012BC64 /* ControlStates.h in Headers */,
                                FD31602912B0267600C1A359 /* ConvolverNode.h in Headers */,
                                D8B6152F1032495100C8554A /* Cookie.h in Headers */,
-                               E4ABABE42361A32900FA4345 /* PropertyCascade.h in Headers */,
                                E1424C94164B52C800F32D40 /* CookieJar.h in Headers */,
                                7A5699702086C619000E0433 /* CookieRequestHeaderFieldProxy.h in Headers */,
                                33D0212D131DB37B004091A8 /* CookieStorage.h in Headers */,
                                1ACADD791880D91C00D8B71D /* ProgressTrackerClient.h in Headers */,
                                F47A633D1FF6FD500081B3CC /* PromisedAttachmentInfo.h in Headers */,
                                A578F4351DE00EEB003DFC6A /* PromiseRejectionEvent.h in Headers */,
+                               E4ABABE42361A32900FA4345 /* PropertyCascade.h in Headers */,
                                E4BBED0F14F4025D003F0B98 /* PropertySetCSSStyleDeclaration.h in Headers */,
                                37BAAE581980D1DD005DFE71 /* ProtectionSpace.h in Headers */,
                                514C76750CE923A1007EF3CD /* ProtectionSpaceBase.h in Headers */,
                                CECADFCE1537791D00E37068 /* TextInsertionBaseCommand.h in Headers */,
                                93309E1C099E64920056E581 /* TextIterator.h in Headers */,
                                9392146918A6D791000EE688 /* TextIteratorBehavior.h in Headers */,
+                               9B02E0C8235EAD2A004044B2 /* TextManipulationController.h in Headers */,
                                BCEF45E90E687767001C1287 /* TextMetrics.h in Headers */,
                                E4D988B417BFD1F60084FB88 /* TextNodeTraversal.h in Headers */,
                                1C18DA59181AF6A500C4EF22 /* TextPainter.h in Headers */,
index 69c491a..43a9046 100644 (file)
 #include "SubresourceLoader.h"
 #include "TextAutoSizing.h"
 #include "TextEvent.h"
+#include "TextManipulationController.h"
 #include "TextNodeTraversal.h"
 #include "TouchAction.h"
 #include "TransformSource.h"
@@ -2478,6 +2479,8 @@ void Document::prepareForDestruction()
 
     m_undoManager->removeAllItems();
 
+    m_textManipulationController = nullptr; // Free nodes kept alive by TextManipulationController.
+
 #if ENABLE(ACCESSIBILITY)
     if (this != &topDocument()) {
         // Let the ax cache know that this subframe goes out of scope.
@@ -8321,4 +8324,11 @@ void Document::setPictureInPictureElement(HTMLVideoElement* element)
 }
 #endif
 
+TextManipulationController& Document::textManipulationController()
+{
+    if (!m_textManipulationController)
+        m_textManipulationController = makeUnique<TextManipulationController>(*this);
+    return *m_textManipulationController;
+}
+
 } // namespace WebCore
index eb7ee0d..42e79f3 100644 (file)
@@ -196,6 +196,7 @@ class StyleSheet;
 class StyleSheetContents;
 class StyleSheetList;
 class Text;
+class TextManipulationController;
 class TextResourceDecoder;
 class TreeWalker;
 class UndoManager;
@@ -1553,6 +1554,9 @@ public:
     void setPictureInPictureElement(HTMLVideoElement*);
 #endif
 
+    WEBCORE_EXPORT TextManipulationController& textManipulationController();
+    TextManipulationController* textManipulationControllerIfExists() { return m_textManipulationController.get(); }
+
 protected:
     enum ConstructionFlags { Synthesized = 1, NonRenderedPlaceholder = 1 << 1 };
     Document(Frame*, const URL&, unsigned = DefaultDocumentClass, unsigned constructionFlags = 0);
@@ -2080,6 +2084,8 @@ private:
     WeakPtr<HTMLVideoElement> m_pictureInPictureElement;
 #endif
 
+    std::unique_ptr<TextManipulationController> m_textManipulationController;
+
     HashMap<Element*, ElementIdentifier> m_identifiedElementsMap;
 };
 
diff --git a/Source/WebCore/editing/TextManipulationController.cpp b/Source/WebCore/editing/TextManipulationController.cpp
new file mode 100644 (file)
index 0000000..2e9d813
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "TextManipulationController.h"
+
+#include "Editing.h"
+#include "ScriptDisallowedScope.h"
+#include "TextIterator.h"
+#include "VisibleUnits.h"
+
+namespace WebCore {
+
+TextManipulationController::TextManipulationController(Document& document)
+    : m_document(makeWeakPtr(document))
+{
+}
+
+void TextManipulationController::startObservingParagraphs(ManipulationItemCallback&& callback)
+{
+    auto document = makeRefPtr(m_document.get());
+    if (!document)
+        return;
+
+    m_callback = WTFMove(callback);
+
+    VisiblePosition start = firstPositionInNode(m_document.get());
+    VisiblePosition end = lastPositionInNode(m_document.get());
+    TextIterator iterator { start.deepEquivalent(), end.deepEquivalent() };
+    if (!document)
+        return; // VisiblePosition or TextIterator's constructor may have updated the layout and executed arbitrary scripts.
+
+    Vector<ManipulationToken> tokensInCurrentParagraph;
+    Position startOfCurrentParagraph = start.deepEquivalent();
+    while (!iterator.atEnd()) {
+        StringView currentText = iterator.text();
+
+        if (startOfCurrentParagraph.isNull())
+            startOfCurrentParagraph = iterator.range()->startPosition();
+
+        size_t endOfLastNewLine = 0;
+        size_t offsetOfNextNewLine = 0;
+        while ((offsetOfNextNewLine = currentText.find('\n', endOfLastNewLine)) != notFound) {
+            if (endOfLastNewLine < offsetOfNextNewLine) {
+                tokensInCurrentParagraph.append(ManipulationToken { m_tokenIdentifier.generate(),
+                    currentText.substring(endOfLastNewLine, offsetOfNextNewLine - endOfLastNewLine).toString() });
+            }
+
+            auto lastRange = iterator.range();
+            if (offsetOfNextNewLine < currentText.length()) {
+                lastRange->setStart(firstPositionInOrBeforeNode(iterator.node())); // Move the start to the beginning of the current node.
+                TextIterator::subrange(lastRange, 0, offsetOfNextNewLine);
+            }
+            Position endOfCurrentParagraph = lastRange->endPosition();
+
+            if (!tokensInCurrentParagraph.isEmpty())
+                addItem(startOfCurrentParagraph, endOfCurrentParagraph, WTFMove(tokensInCurrentParagraph));
+            startOfCurrentParagraph.clear();
+            endOfLastNewLine = offsetOfNextNewLine + 1;
+        }
+
+        auto remainingText = currentText.substring(endOfLastNewLine);
+        if (remainingText.length())
+            tokensInCurrentParagraph.append(ManipulationToken { m_tokenIdentifier.generate(), remainingText.toString() });
+
+        iterator.advance();
+    }
+
+    if (!tokensInCurrentParagraph.isEmpty())
+        addItem(startOfCurrentParagraph, end.deepEquivalent(), WTFMove(tokensInCurrentParagraph));
+}
+
+void TextManipulationController::addItem(const Position& startOfParagraph, const Position& endOfParagraph, Vector<ManipulationToken>&& tokens)
+{
+    ASSERT(m_document);
+    auto result = m_items.add(m_itemIdentifier.generate(), ManipulationItem { startOfParagraph, endOfParagraph, WTFMove(tokens) });
+    m_callback(*m_document, result.iterator->key, result.iterator->value.tokens);
+}
+
+bool TextManipulationController::completeManipulation(ItemIdentifier itemIdentifier, const Vector<ManipulationToken>& replacementTokens)
+{
+    if (!itemIdentifier)
+        return false;
+
+    auto itemIterator = m_items.find(itemIdentifier);
+    if (itemIterator == m_items.end())
+        return false;
+
+    auto didReplace = replace(itemIterator->value, replacementTokens);
+
+    m_items.remove(itemIterator);
+
+    return didReplace;
+}
+
+bool TextManipulationController::replace(const ManipulationItem& item, const Vector<ManipulationToken>& replacementTokens)
+{
+    TextIterator iterator { item.start, item.end };
+    size_t currentTokenIndex = 0;
+    HashMap<TokenIdentifier, Ref<Node>> tokenToNode;
+    while (!iterator.atEnd()) {
+        auto string = iterator.text().toString();
+        if (currentTokenIndex >= item.tokens.size())
+            return false;
+        auto& currentToken = item.tokens[currentTokenIndex];
+        if (iterator.text() != currentToken.content)
+            return false;
+        tokenToNode.add(currentToken.identifier, *iterator.node());
+        iterator.advance();
+        ++currentTokenIndex;
+    }
+
+    // FIXME: This doesn't preseve the order of the replacement at all.
+    for (auto& token : replacementTokens) {
+        auto* node = tokenToNode.get(token.identifier);
+        if (!node)
+            return false;
+        if (!is<CharacterData>(node))
+            continue;
+        // FIXME: It's not safe to update DOM while iterating over the tokens.
+        downcast<CharacterData>(node)->setData(token.content);
+    }
+
+    return true;
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/editing/TextManipulationController.h b/Source/WebCore/editing/TextManipulationController.h
new file mode 100644 (file)
index 0000000..4657974
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Position.h"
+#include <wtf/CompletionHandler.h>
+#include <wtf/ObjectIdentifier.h>
+#include <wtf/Optional.h>
+#include <wtf/WeakPtr.h>
+
+namespace WebCore {
+
+class Document;
+
+class TextManipulationController : public CanMakeWeakPtr<TextManipulationController> {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    TextManipulationController(Document&);
+
+    enum TokenIdentifierType { };
+    using TokenIdentifier = ObjectIdentifier<TokenIdentifierType>;
+
+    struct ManipulationToken {
+        TokenIdentifier identifier;
+        String content;
+
+        template<class Encoder> void encode(Encoder&) const;
+        template<class Decoder> static Optional<ManipulationToken> decode(Decoder&);
+    };
+
+    enum ItemIdentifierType { };
+    using ItemIdentifier = ObjectIdentifier<ItemIdentifierType>;
+
+    using ManipulationItemCallback = WTF::Function<void(Document&, ItemIdentifier, const Vector<ManipulationToken>&)>;
+    WEBCORE_EXPORT void startObservingParagraphs(ManipulationItemCallback&&);
+
+    WEBCORE_EXPORT bool completeManipulation(ItemIdentifier, const Vector<ManipulationToken>&);
+
+private:
+    struct ManipulationItem {
+        Position start;
+        Position end;
+        Vector<ManipulationToken> tokens;
+    };
+
+    void addItem(const Position& startOfParagraph, const Position& endOfParagraph, Vector<ManipulationToken>&&);
+    bool replace(const ManipulationItem&, const Vector<ManipulationToken>&);
+
+    WeakPtr<Document> m_document;
+    ManipulationItemCallback m_callback;
+    HashMap<ItemIdentifier, ManipulationItem> m_items;
+    ItemIdentifier m_itemIdentifier;
+    TokenIdentifier m_tokenIdentifier;
+};
+
+template<class Encoder>
+void TextManipulationController::ManipulationToken::encode(Encoder& encoder) const
+{
+    encoder << identifier << content;
+}
+
+template<class Decoder>
+Optional<TextManipulationController::ManipulationToken> TextManipulationController::ManipulationToken::decode(Decoder& decoder)
+{
+    ManipulationToken result;
+    if (!decoder.decode(result.identifier))
+        return WTF::nullopt;
+    if (!decoder.decode(result.content))
+        return WTF::nullopt;
+    return result;
+}
+
+} // namespace WebCore
index a5b74ee..d64f66d 100644 (file)
@@ -1,3 +1,49 @@
+2019-10-23  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Add a mechanism to find and manipulate text by paragraphs
+        https://bugs.webkit.org/show_bug.cgi?id=203286
+
+        Reviewed by Wenson Hsieh.
+
+        This patch introduces a new SPI to find paragraphs of text and let client replace the content of each paragraph.
+        For now, this SPI is limited to find & replace contents of main frame's document.
+
+        WKWebView's _startTextManipulationsWithCompletionHandler sends StartTextManipulations message to WebContent process
+        to find all paragraphs in the main frame. WebContent process will send back DidFindTextManipulationItem message
+        for each paragraph, which in turn calls back _WKTextManipulationDelegate's _webView: didFindTextManipulationItem:.
+
+        Upon receiving this delegate callback, the client can invoke WKWebView's _completeTextManipulation to replace
+        the content. It will send CompleteTextManipulation to WebContent process, which will invoke completeManipulation
+        on main frame's document's TextManipulationController.
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _textManipulationDelegate]): Aded.
+        (-[WKWebView _setTextManipulationDelegate:]): Aded.
+        (-[WKWebView _startTextManipulationsWithCompletionHandler:]):
+        (-[WKWebView _completeTextManipulation:completion:]):
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+        * UIProcess/API/Cocoa/_WKTextManipulationDelegate.h: Added.
+        * UIProcess/API/Cocoa/_WKTextManipulationItem.h: Added.
+        * UIProcess/API/Cocoa/_WKTextManipulationItem.mm: Added.
+        (-[_WKTextManipulationItem initWithIdentifier:tokens:]):
+        (-[_WKTextManipulationItem identifier]):
+        (-[_WKTextManipulationItem tokens]):
+        * UIProcess/API/Cocoa/_WKTextManipulationToken.h: Added.
+        * UIProcess/API/Cocoa/_WKTextManipulationToken.mm: Added.
+        (-[_WKTextManipulationToken init]):
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::startTextManipulations):
+        (WebKit::WebPageProxy::didFindTextManipulationItem):
+        (WebKit::WebPageProxy::completeTextManipulation):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/WebPageProxy.messages.in:
+        * WebKit.xcodeproj/project.pbxproj:
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::startTextManipulations):
+        (WebKit::WebPage::completeTextManipulation):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+
 2019-10-24  Matt Lewis  <jlewis3@apple.com>
 
         Unreviewed, rolling out r251558.
index c9528c2..2d46ccc 100644 (file)
@@ -260,6 +260,8 @@ UIProcess/API/Cocoa/_WKProcessPoolConfiguration.mm
 UIProcess/API/Cocoa/_WKRemoteWebInspectorViewController.mm
 UIProcess/API/Cocoa/_WKSessionState.mm
 UIProcess/API/Cocoa/_WKTextInputContext.mm
+UIProcess/API/Cocoa/_WKTextManipulationItem.mm
+UIProcess/API/Cocoa/_WKTextManipulationToken.mm
 UIProcess/API/Cocoa/_WKThumbnailView.mm
 UIProcess/API/Cocoa/_WKUserContentExtensionStore.mm
 UIProcess/API/Cocoa/_WKUserContentFilter.mm
index 0261c49..101172f 100644 (file)
@@ -99,6 +99,9 @@
 #import "_WKRemoteObjectRegistryInternal.h"
 #import "_WKSessionStateInternal.h"
 #import "_WKTextInputContextInternal.h"
+#import "_WKTextManipulationDelegate.h"
+#import "_WKTextManipulationItem.h"
+#import "_WKTextManipulationToken.h"
 #import "_WKVisitedLinkStoreInternal.h"
 #import "_WKWebsitePoliciesInternal.h"
 #import <WebCore/ElementContext.h>
 #import <WebCore/Settings.h>
 #import <WebCore/SharedBuffer.h>
 #import <WebCore/StringUtilities.h>
+#import <WebCore/TextManipulationController.h>
 #import <WebCore/ValidationBubble.h>
 #import <WebCore/ViewportArguments.h>
 #import <WebCore/WritingMode.h>
@@ -277,6 +281,8 @@ static Optional<WebCore::ScrollbarOverlayStyle> toCoreScrollbarStyle(_WKOverlayS
 
     BOOL _usePlatformFindUI;
 
+    WeakObjCPtr<id <_WKTextManipulationDelegate>> _textManipulationDelegate;
+
 #if PLATFORM(IOS_FAMILY)
     RetainPtr<_WKRemoteObjectRegistry> _remoteObjectRegistry;
 
@@ -7461,6 +7467,65 @@ static WebCore::UserInterfaceLayoutDirection toUserInterfaceLayoutDirection(UISe
     });
 }
 
+- (id <_WKTextManipulationDelegate>)_textManipulationDelegate
+{
+    return _textManipulationDelegate.getAutoreleased();
+}
+
+- (void)_setTextManipulationDelegate:(id <_WKTextManipulationDelegate>)delegate
+{
+    _textManipulationDelegate = delegate;
+}
+
+- (void)_startTextManipulationsWithCompletionHandler:(void(^)())completionHandler
+{
+    if (!_textManipulationDelegate)
+        return;
+    if (!_page)
+        return;
+    _page->startTextManipulations([weakSelf = WeakObjCPtr<WKWebView>(self)] (WebCore::TextManipulationController::ItemIdentifier itemID,
+        const Vector<WebCore::TextManipulationController::ManipulationToken>& tokens) {
+        if (!weakSelf)
+            return;
+
+        auto retainedSelf = weakSelf.get();
+        auto delegate = [retainedSelf _textManipulationDelegate];
+        if (!delegate)
+            return;
+
+        NSMutableArray *wkTokens = [NSMutableArray arrayWithCapacity:tokens.size()];
+        for (auto& token : tokens) {
+            auto wkToken = adoptNS([[_WKTextManipulationToken alloc] init]);
+            [wkToken setIdentifier:String::number(token.identifier.toUInt64())];
+            [wkToken setContent:token.content];
+            [wkTokens addObject:wkToken.get()];
+        }
+
+        auto item = adoptNS([[_WKTextManipulationItem alloc] initWithIdentifier:String::number(itemID.toUInt64()) tokens:wkTokens]);
+        [delegate _webView:retainedSelf.get() didFindTextManipulationItem:item.get()];
+    }, [capturedCompletionBlock = makeBlockPtr(completionHandler)] () {
+        capturedCompletionBlock();
+    });
+}
+
+- (void)_completeTextManipulation:(_WKTextManipulationItem *)item completion:(void(^)(BOOL success))completionHandler
+{
+    if (!_page)
+        return;
+
+    auto itemID = makeObjectIdentifier<WebCore::TextManipulationController::ItemIdentifierType>(String(item.identifier).toUInt64());
+
+    Vector<WebCore::TextManipulationController::ManipulationToken> tokens;
+    for (_WKTextManipulationToken *wkToken in item.tokens) {
+        auto tokenID = makeObjectIdentifier<WebCore::TextManipulationController::TokenIdentifierType>(String(wkToken.identifier).toUInt64());
+        tokens.append(WebCore::TextManipulationController::ManipulationToken { tokenID, wkToken.content });
+    }
+
+    _page->completeTextManipulation(itemID, tokens, [capturedCompletionBlock = makeBlockPtr(completionHandler)] (bool success) {
+        capturedCompletionBlock(success);
+    });
+}
+
 #if PLATFORM(IOS_FAMILY)
 
 - (CGRect)_dragCaretRect
index 3dfc6b6..5778ab8 100644 (file)
@@ -113,6 +113,7 @@ typedef NS_OPTIONS(NSUInteger, _WKRectEdge) {
 @class _WKSafeBrowsingWarning;
 @class _WKSessionState;
 @class _WKTextInputContext;
+@class _WKTextManipulationItem;
 @class _WKThumbnailView;
 @class _WKWebsitePolicies;
 @class _WKWebViewPrintFormatter;
@@ -120,9 +121,10 @@ typedef NS_OPTIONS(NSUInteger, _WKRectEdge) {
 @protocol WKHistoryDelegatePrivate;
 @protocol _WKDiagnosticLoggingDelegate;
 @protocol _WKFindDelegate;
+@protocol _WKFullscreenDelegate;
 @protocol _WKIconLoadingDelegate;
 @protocol _WKInputDelegate;
-@protocol _WKFullscreenDelegate;
+@protocol _WKTextManipulationDelegate;
 
 @interface WKWebView (WKPrivate)
 
@@ -575,6 +577,10 @@ typedef NS_OPTIONS(NSUInteger, _WKRectEdge) {
 @property (nonatomic, readonly) _WKInspector *_inspector WK_API_AVAILABLE(macos(10.14.4), ios(12.2));
 @property (nonatomic, readonly) _WKFrameHandle *_mainFrame WK_API_AVAILABLE(macos(10.14.4), ios(12.2));
 
+@property (nonatomic, weak, setter=_setTextManipulationDelegate:) id <_WKTextManipulationDelegate> _textManipulationDelegate WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+- (void)_startTextManipulationsWithCompletionHandler:(void(^)(void))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+- (void)_completeTextManipulation:(_WKTextManipulationItem *)item completion:(void(^)(BOOL success))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
+
 @property (nonatomic, setter=_setScrollingUpdatesDisabledForTesting:) BOOL _scrollingUpdatesDisabledForTesting WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 - (void)_processWillSuspendImminentlyForTesting;
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationDelegate.h b/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationDelegate.h
new file mode 100644 (file)
index 0000000..70ab49e
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <WebKit/WKFoundation.h>
+
+#import <Foundation/Foundation.h>
+
+@class _WKTextManipulationItem;
+
+@protocol _WKTextManipulationDelegate <NSObject>
+
+@optional
+
+- (void)_webView:(WKWebView *)webView didFindTextManipulationItem:(_WKTextManipulationItem *)item;
+
+@end
+
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationItem.h b/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationItem.h
new file mode 100644 (file)
index 0000000..441bb6d
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <WebKit/WKFoundation.h>
+
+#import <Foundation/Foundation.h>
+
+@class _WKTextManipulationToken;
+
+WK_CLASS_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA))
+@interface _WKTextManipulationItem : NSObject
+
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initWithIdentifier:(NSString *)identifier tokens:(NSArray<_WKTextManipulationToken *> *)tokens;
+
+@property (nonatomic, readonly, copy) NSString *identifier;
+@property (nonatomic, readonly, copy) NSArray<_WKTextManipulationToken *> *tokens;
+
+@end
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationItem.mm b/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationItem.mm
new file mode 100644 (file)
index 0000000..b338876
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+#import "_WKTextManipulationItem.h"
+
+#import <wtf/RetainPtr.h>
+
+@implementation _WKTextManipulationItem {
+    RetainPtr<NSString> _identifier;
+    RetainPtr<NSArray<_WKTextManipulationToken *>> _tokens;
+}
+
+- (instancetype)initWithIdentifier:(NSString *)identifier tokens:(NSArray<_WKTextManipulationToken *> *)tokens
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _identifier = identifier;
+    _tokens = tokens;
+    return self;
+}
+
+- (NSString *)identifier
+{
+    return _identifier.get();
+}
+
+- (NSArray<_WKTextManipulationToken *> *)tokens
+{
+    return _tokens.get();
+}
+
+@end
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationToken.h b/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationToken.h
new file mode 100644 (file)
index 0000000..0b85eae
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <WebKit/WKFoundation.h>
+
+#import <Foundation/Foundation.h>
+
+WK_CLASS_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA))
+@interface _WKTextManipulationToken : NSObject
+
+@property (nonatomic, copy) NSString *identifier;
+@property (nonatomic, copy) NSString *content;
+
+@end
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationToken.mm b/Source/WebKit/UIProcess/API/Cocoa/_WKTextManipulationToken.mm
new file mode 100644 (file)
index 0000000..a50a8ea
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+#import "_WKTextManipulationToken.h"
+
+#import <wtf/RetainPtr.h>
+
+@implementation _WKTextManipulationToken
+@end
index ac91cd6..991861d 100644 (file)
@@ -27,6 +27,8 @@
 #import "DownloadProxyMap.h"
 
 #if PLATFORM(IOS_FAMILY)
+#import <wtf/BlockPtr.h>
+#import <wtf/WeakPtr.h>
 #import <UIKit/UIKit.h>
 #endif
 
index 7d122d5..69f86f9 100644 (file)
@@ -34,6 +34,7 @@
 #import "APIUIClient.h"
 #import "SOAuthorizationLoadPolicy.h"
 #import "WKUIDelegatePrivate.h"
+#import "WKWebViewInternal.h"
 #import "WebPageProxy.h"
 #import "WebsiteDataStore.h"
 #import <WebCore/ResourceResponse.h>
index 0a427f7..6cc6dcd 100644 (file)
@@ -41,6 +41,7 @@
 #import "WebMemoryPressureHandler.h"
 #import "WebPageGroup.h"
 #import "WebPreferencesKeys.h"
+#import "WebProcessCache.h"
 #import "WebProcessCreationParameters.h"
 #import "WebProcessMessages.h"
 #import "WindowServerConnection.h"
index 3bdf2e3..8a06d7f 100644 (file)
@@ -9419,6 +9419,34 @@ void WebPageProxy::setMockWebAuthenticationConfiguration(MockWebAuthenticationCo
 }
 #endif
 
+void WebPageProxy::startTextManipulations(TextManipulationItemCallback&& callback, WTF::CompletionHandler<void()>&& completionHandler)
+{
+    if (!hasRunningProcess()) {
+        completionHandler();
+        return;
+    }
+    m_textManipulationItemCallback = WTFMove(callback);
+    m_process->connection()->sendWithAsyncReply(Messages::WebPage::StartTextManipulations(), WTFMove(completionHandler), m_webPageID);
+}
+
+void WebPageProxy::didFindTextManipulationItem(WebCore::TextManipulationController::ItemIdentifier itemID,
+    const Vector<WebCore::TextManipulationController::ManipulationToken>& tokens)
+{
+    if (!m_textManipulationItemCallback)
+        return;
+    m_textManipulationItemCallback(itemID, tokens);
+}
+
+void WebPageProxy::completeTextManipulation(WebCore::TextManipulationController::ItemIdentifier itemID,
+    const Vector<WebCore::TextManipulationController::ManipulationToken>& tokens, WTF::Function<void (bool success)>&& completionHandler)
+{
+    if (!hasRunningProcess()) {
+        completionHandler(false);
+        return;
+    }
+    m_process->connection()->sendWithAsyncReply(Messages::WebPage::CompleteTextManipulation(itemID, tokens), WTFMove(completionHandler), m_webPageID);
+}
+
 } // namespace WebKit
 
 #undef MERGE_WHEEL_EVENTS
index a5a3454..d47b7f4 100644 (file)
@@ -77,6 +77,7 @@
 #include <WebCore/FontAttributes.h>
 #include <WebCore/FrameLoaderTypes.h>
 #include <WebCore/FrameView.h> // FIXME: Move LayoutViewportConstraint to its own file and stop including this.
+#include <WebCore/GlobalFrameIdentifier.h>
 #include <WebCore/InputMode.h>
 #include <WebCore/LayoutPoint.h>
 #include <WebCore/LayoutSize.h>
@@ -93,6 +94,7 @@
 #include <WebCore/SearchPopupMenu.h>
 #include <WebCore/TextChecking.h>
 #include <WebCore/TextGranularity.h>
+#include <WebCore/TextManipulationController.h>
 #include <WebCore/UserInterfaceLayoutDirection.h>
 #include <WebCore/ViewportArguments.h>
 #include <memory>
@@ -1611,6 +1613,12 @@ public:
     void setMockWebAuthenticationConfiguration(WebCore::MockWebAuthenticationConfiguration&&);
 #endif
 
+    using TextManipulationItemCallback = WTF::Function<void (WebCore::TextManipulationController::ItemIdentifier, const Vector<WebCore::TextManipulationController::ManipulationToken>&)>;
+    void startTextManipulations(TextManipulationItemCallback&&, WTF::CompletionHandler<void()>&&);
+    void didFindTextManipulationItem(WebCore::TextManipulationController::ItemIdentifier, const Vector<WebCore::TextManipulationController::ManipulationToken>&);
+    void completeTextManipulation(WebCore::TextManipulationController::ItemIdentifier,
+        const Vector<WebCore::TextManipulationController::ManipulationToken>&, WTF::Function<void (bool success)>&&);
+
 private:
     WebPageProxy(PageClient&, WebProcessProxy&, Ref<API::PageConfiguration>&&);
     void platformInitialize();
@@ -2574,6 +2582,8 @@ private:
     std::unique_ptr<SuspendedPageProxy> m_suspendedPageKeptToPreventFlashing;
     WeakPtr<SuspendedPageProxy> m_lastSuspendedPage;
 
+    TextManipulationItemCallback m_textManipulationItemCallback;
+
 #if HAVE(PENCILKIT)
     std::unique_ptr<EditableImageController> m_editableImageController;
 #endif
index 7b9fe20..6c62307 100644 (file)
@@ -581,4 +581,7 @@ messages -> WebPageProxy {
     SendMessageToWebView(struct WebKit::UserMessage userMessage)
     SendMessageToWebViewWithReply(struct WebKit::UserMessage userMessage) -> (struct WebKit::UserMessage replyMessage) Async
 #endif
+
+    DidFindTextManipulationItem(WebCore::TextManipulationController::ItemIdentifier itemID, Vector<WebCore::TextManipulationController::ManipulationToken> tokens)
+
 }
index 34a69bb..825f83a 100644 (file)
@@ -46,6 +46,7 @@
 #import <WebCore/VideoFullscreenModel.h>
 #import <WebCore/WebCoreFullScreenPlaceholderView.h>
 #import <WebCore/WebCoreFullScreenWindow.h>
+#import <pal/spi/cg/CoreGraphicsSPI.h>
 #import <pal/system/SleepDisabler.h>
 #import <wtf/BlockObjCExceptions.h>
 
index bdc918f..f77a7d4 100644 (file)
                99E714C51C124A0400665B3A /* _WKAutomationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 99E714C11C1249E600665B3A /* _WKAutomationDelegate.h */; settings = {ATTRIBUTES = (Private, ); }; };
                99E7189A21F79D9E0055E975 /* _WKTouchEventGenerator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 99E7189621F79D9D0055E975 /* _WKTouchEventGenerator.mm */; };
                99E7189C21F79D9E0055E975 /* _WKTouchEventGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = 99E7189821F79D9E0055E975 /* _WKTouchEventGenerator.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               9B02E0CB235EB953004044B2 /* _WKTextManipulationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B02E0C9235EB62D004044B2 /* _WKTextManipulationDelegate.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               9B02E0CC235EB957004044B2 /* _WKTextManipulationItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B02E0CA235EB884004044B2 /* _WKTextManipulationItem.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               9B02E0D7235FC94F004044B2 /* _WKTextManipulationToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 9B02E0CD235EB967004044B2 /* _WKTextManipulationToken.h */; settings = {ATTRIBUTES = (Private, ); }; };
                9FB5F395169E6A80002C25BF /* WKContextPrivateMac.h in Headers */ = {isa = PBXBuildFile; fileRef = 9FB5F393169E6A80002C25BF /* WKContextPrivateMac.h */; settings = {ATTRIBUTES = (Private, ); }; };
                A102A7081EC0EEE900D81D82 /* com.macromedia.Flash Player ESR.plugin.sb in Copy Plug-in Sandbox Profiles */ = {isa = PBXBuildFile; fileRef = 7A5E39491D5BD8A700B4B7CE /* com.macromedia.Flash Player ESR.plugin.sb */; };
                A1046EA12079263100F0C5D8 /* WKPDFView.h in Headers */ = {isa = PBXBuildFile; fileRef = A1046E9F2079263100F0C5D8 /* WKPDFView.h */; };
                        compilerSpec = com.apple.compilers.proxy.script;
                        filePatterns = "*.h";
                        fileType = pattern.proxy;
-                       inputFiles = (
-                       );
                        isEditable = 1;
                        outputFiles = (
                                "$(HEADER_OUTPUT_DIR)/$(INPUT_FILE_NAME)",
                99E7189621F79D9D0055E975 /* _WKTouchEventGenerator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKTouchEventGenerator.mm; sourceTree = "<group>"; };
                99E7189821F79D9E0055E975 /* _WKTouchEventGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _WKTouchEventGenerator.h; sourceTree = "<group>"; };
                99F642D21FABE378009621E9 /* CoordinateSystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoordinateSystem.h; sourceTree = "<group>"; };
+               9B02E0C9235EB62D004044B2 /* _WKTextManipulationDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _WKTextManipulationDelegate.h; sourceTree = "<group>"; };
+               9B02E0CA235EB884004044B2 /* _WKTextManipulationItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _WKTextManipulationItem.h; sourceTree = "<group>"; };
+               9B02E0CD235EB967004044B2 /* _WKTextManipulationToken.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _WKTextManipulationToken.h; sourceTree = "<group>"; };
+               9B02E0CE235EBB14004044B2 /* _WKTextManipulationToken.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKTextManipulationToken.mm; sourceTree = "<group>"; };
+               9B02E0D0235EBCCA004044B2 /* _WKTextManipulationItem.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKTextManipulationItem.mm; sourceTree = "<group>"; };
                9BC59D6C1EFCCCB6001E8D09 /* CallbackID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallbackID.h; sourceTree = "<group>"; };
                9BC59D6D1EFCDC6D001E8D09 /* OptionalCallbackID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OptionalCallbackID.h; sourceTree = "<group>"; };
                9F54F88E16488E87007DF81A /* AuxiliaryProcessMac.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AuxiliaryProcessMac.mm; sourceTree = "<group>"; };
                                2DE9B1382231F61C005287B7 /* _WKTextInputContext.h */,
                                2DE9B1372231F61C005287B7 /* _WKTextInputContext.mm */,
                                2DE9B13B2231F77C005287B7 /* _WKTextInputContextInternal.h */,
+                               9B02E0C9235EB62D004044B2 /* _WKTextManipulationDelegate.h */,
+                               9B02E0CA235EB884004044B2 /* _WKTextManipulationItem.h */,
+                               9B02E0D0235EBCCA004044B2 /* _WKTextManipulationItem.mm */,
+                               9B02E0CD235EB967004044B2 /* _WKTextManipulationToken.h */,
+                               9B02E0CE235EBB14004044B2 /* _WKTextManipulationToken.mm */,
                                2D6B371918A967AD0042AE80 /* _WKThumbnailView.h */,
                                2D6B371A18A967AD0042AE80 /* _WKThumbnailView.mm */,
                                2DACE64D18ADBFF000E4CA76 /* _WKThumbnailViewInternal.h */,
                                1A002D43196B337000B9AD44 /* _WKSessionStateInternal.h in Headers */,
                                2DE9B13A2231F61C005287B7 /* _WKTextInputContext.h in Headers */,
                                2DE9B13C2231F77C005287B7 /* _WKTextInputContextInternal.h in Headers */,
+                               9B02E0CB235EB953004044B2 /* _WKTextManipulationDelegate.h in Headers */,
+                               9B02E0CC235EB957004044B2 /* _WKTextManipulationItem.h in Headers */,
+                               9B02E0D7235FC94F004044B2 /* _WKTextManipulationToken.h in Headers */,
                                2D6B371B18A967AD0042AE80 /* _WKThumbnailView.h in Headers */,
                                2DACE64E18ADBFF000E4CA76 /* _WKThumbnailViewInternal.h in Headers */,
                                99E7189C21F79D9E0055E975 /* _WKTouchEventGenerator.h in Headers */,
index d5ecf20..d7d80b6 100644 (file)
@@ -6836,6 +6836,51 @@ Optional<WebCore::ElementContext> WebPage::contextForElement(WebCore::Element& e
     return WebCore::ElementContext { elementRectInRootViewCoordinates(element, *frame), m_identifier, document.identifier(), document.identifierForElement(element) };
 }
 
+void WebPage::startTextManipulations(CompletionHandler<void()>&& completionHandler)
+{
+    if (!m_page)
+        return;
+
+    auto mainDocument = makeRefPtr(m_page->mainFrame().document());
+    if (!mainDocument)
+        return;
+
+    mainDocument->textManipulationController().startObservingParagraphs([webPage = makeWeakPtr(*this)] (WebCore::Document& document,
+        WebCore::TextManipulationController::ItemIdentifier itemIdentifier, const Vector<WebCore::TextManipulationController::ManipulationToken>& tokens) {
+        auto* frame = document.frame();
+        if (!webPage || !frame || webPage->mainFrame() != frame)
+            return;
+
+        auto* webFrame = WebFrame::fromCoreFrame(*frame);
+        if (!webFrame)
+            return;
+
+        webPage->send(Messages::WebPageProxy::DidFindTextManipulationItem(itemIdentifier, tokens));
+    });
+    // For now, we assume startObservingParagraphs find all paragraphs synchronously at once.
+    completionHandler();
+}
+
+void WebPage::completeTextManipulation(WebCore::TextManipulationController::ItemIdentifier itemID,
+    const Vector<WebCore::TextManipulationController::ManipulationToken>& tokens, CompletionHandler<void(bool)>&& completionHandler)
+{
+    auto completeManipulation = [&] {
+        if (!m_page)
+            return false;
+
+        auto mainDocument = makeRefPtr(m_page->mainFrame().document());
+        if (!mainDocument)
+            return false;
+
+        auto* controller = mainDocument->textManipulationControllerIfExists();
+        if (!controller)
+            return false;
+
+        return controller->completeManipulation(itemID, tokens);
+    };
+    completionHandler(completeManipulation());
+}
+
 PAL::SessionID WebPage::sessionID() const
 {
     return WebProcess::singleton().sessionID();
index e683ec9..43ec3b3 100644 (file)
@@ -73,6 +73,7 @@
 #include <WebCore/PointerID.h>
 #include <WebCore/SecurityPolicyViolationEvent.h>
 #include <WebCore/ShareData.h>
+#include <WebCore/TextManipulationController.h>
 #include <WebCore/UserActivity.h>
 #include <WebCore/UserContentTypes.h>
 #include <WebCore/UserInterfaceLayoutDirection.h>
@@ -1196,6 +1197,10 @@ public:
     WebCore::Element* elementForContext(const WebCore::ElementContext&) const;
     Optional<WebCore::ElementContext> contextForElement(WebCore::Element&) const;
 
+    void startTextManipulations(CompletionHandler<void()>&&);
+    void completeTextManipulation(WebCore::TextManipulationController::ItemIdentifier,
+        const Vector<WebCore::TextManipulationController::ManipulationToken>&, CompletionHandler<void(bool)>&&);
+
 #if ENABLE(APPLE_PAY)
     WebPaymentCoordinator* paymentCoordinator();
 #endif
index 380eb0c..1f985a0 100644 (file)
@@ -582,4 +582,7 @@ GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType
     SendMessageToWebExtension(struct WebKit::UserMessage userMessage)
     SendMessageToWebExtensionWithReply(struct WebKit::UserMessage userMessage) -> (struct WebKit::UserMessage replyMessage) Async
 #endif
+
+    StartTextManipulations() -> () Async
+    CompleteTextManipulation(WebCore::TextManipulationController::ItemIdentifier itemID, Vector<WebCore::TextManipulationController::ManipulationToken> tokens) -> (bool success) Async
 }
index 43bc606..0bf8f36 100644 (file)
@@ -1,3 +1,19 @@
+2019-10-23  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Add a mechanism to find and manipulate text by paragraphs
+        https://bugs.webkit.org/show_bug.cgi?id=203286
+
+        Reviewed by Wenson Hsieh.
+
+        Added basic API tests for the new SPI.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm: Added.
+        (-[TextManipulationDelegate init]):
+        (-[TextManipulationDelegate _webView:didFindItem:forFrame:]):
+        (-[TextManipulationDelegate items]):
+        (TestWebKitAPI::createItem):
+
 2019-10-24  Matt Lewis  <jlewis3@apple.com>
 
         Unreviewed, rolling out r251558.
index d666ad7..e2202a0 100644 (file)
                9984FACC1CFFAF60008D198C /* WKWebViewTextInput.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9984FACA1CFFAEEE008D198C /* WKWebViewTextInput.mm */; };
                9984FACE1CFFB090008D198C /* editable-body.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = 9984FACD1CFFB038008D198C /* editable-body.html */; };
                9999108B1F393C96008AD455 /* Copying.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9999108A1F393C8B008AD455 /* Copying.mm */; };
+               9B02E0D6235FA47D004044B2 /* TextManipulation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9B02E0D5235FA47D004044B2 /* TextManipulation.mm */; };
                9B0786A51C5885C300D159E3 /* InjectedBundleMakeAllShadowRootsOpen_Bundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9B0786A41C5885C300D159E3 /* InjectedBundleMakeAllShadowRootsOpen_Bundle.cpp */; };
                9B19CDA01F06DFE3000548DD /* NetworkProcessCrashWithPendingConnection.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9B19CD9E1F06DFE3000548DD /* NetworkProcessCrashWithPendingConnection.mm */; };
                9B1F6F781F90558400B55744 /* CopyHTML.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9B1056411F9045C700D5583F /* CopyHTML.mm */; };
                        dstPath = TestWebKitAPI.resources;
                        dstSubfolderSpec = 7;
                        files = (
-                               5798337E236019A4008E5547 /* web-authentication-make-credential-hid.html in Copy Resources */,
                                55A817FF2181021A0004A39A /* 100x100-red.tga in Copy Resources */,
                                1A9E52C913E65EF4006917F5 /* 18-characters.html in Copy Resources */,
                                55A81800218102210004A39A /* 400x400-green.png in Copy Resources */,
                                57663DEA234EA66D00E85E09 /* web-authentication-get-assertion-nfc.html in Copy Resources */,
                                577454D22359BB01008E1ED7 /* web-authentication-get-assertion-u2f-no-credentials.html in Copy Resources */,
                                57C624502346C21E00383FE7 /* web-authentication-get-assertion.html in Copy Resources */,
+                               5798337E236019A4008E5547 /* web-authentication-make-credential-hid.html in Copy Resources */,
                                1C2B81861C89259D00A5529F /* webfont.html in Copy Resources */,
                                51714EB41CF8C78C004723C4 /* WebProcessKillIDBCleanup-1.html in Copy Resources */,
                                51714EB51CF8C78C004723C4 /* WebProcessKillIDBCleanup-2.html in Copy Resources */,
                9984FACA1CFFAEEE008D198C /* WKWebViewTextInput.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WKWebViewTextInput.mm; sourceTree = "<group>"; };
                9984FACD1CFFB038008D198C /* editable-body.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "editable-body.html"; sourceTree = "<group>"; };
                9999108A1F393C8B008AD455 /* Copying.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Copying.mm; sourceTree = "<group>"; };
+               9B02E0D5235FA47D004044B2 /* TextManipulation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TextManipulation.mm; sourceTree = "<group>"; };
                9B0786A21C58830F00D159E3 /* InjectedBundleMakeAllShadowRootsOpen.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InjectedBundleMakeAllShadowRootsOpen.cpp; sourceTree = "<group>"; };
                9B0786A41C5885C300D159E3 /* InjectedBundleMakeAllShadowRootsOpen_Bundle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InjectedBundleMakeAllShadowRootsOpen_Bundle.cpp; sourceTree = "<group>"; };
                9B1056411F9045C700D5583F /* CopyHTML.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CopyHTML.mm; sourceTree = "<group>"; };
                                5774AA6721FBBF7800AF2A1B /* TestSOAuthorization.mm */,
                                F4CD74C720FDB49600DE3794 /* TestURLSchemeHandler.h */,
                                F4CD74C820FDB49600DE3794 /* TestURLSchemeHandler.mm */,
+                               9B02E0D5235FA47D004044B2 /* TextManipulation.mm */,
                                5C16F8FB230C942B0074C4A8 /* TextSize.mm */,
                                C22FA32A228F8708009D7988 /* TextWidth.mm */,
                                5C73A81A2323059800DEA85A /* TLSDeprecation.mm */,
                                F45033F5206BEC95009351CE /* TextAutosizingBoost.mm in Sources */,
                                93E6193B1F931B3A00AF245E /* TextCodec.cpp in Sources */,
                                CE3524F91B1441C40028A7C5 /* TextFieldDidBeginAndEndEditing.cpp in Sources */,
+                               9B02E0D6235FA47D004044B2 /* TextManipulation.mm in Sources */,
                                5C16F8FC230C94370074C4A8 /* TextSize.mm in Sources */,
                                C22FA32B228F8708009D7988 /* TextWidth.mm in Sources */,
                                7CCE7EDD1A411A9200447C4C /* TimeRanges.cpp in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/TextManipulation.mm
new file mode 100644 (file)
index 0000000..da23efc
--- /dev/null
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#import "PlatformUtilities.h"
+#import "Test.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKWebViewPrivate.h>
+#import <WebKit/_WKTextManipulationDelegate.h>
+#import <WebKit/_WKTextManipulationItem.h>
+#import <WebKit/_WKTextManipulationToken.h>
+#import <wtf/BlockPtr.h>
+#import <wtf/RetainPtr.h>
+
+static bool done = false;
+
+@interface TextManipulationDelegate : NSObject <_WKTextManipulationDelegate>
+
+- (void)_webView:(WKWebView *)webView didFindTextManipulationItem:(_WKTextManipulationItem *)item;
+
+@property (nonatomic, readonly, copy) NSArray<_WKTextManipulationItem *> *items;
+
+@end
+
+@implementation TextManipulationDelegate {
+    RetainPtr<NSMutableArray> _items;
+}
+
+- (instancetype)init
+{
+    if (!(self = [super init]))
+        return nil;
+    _items = adoptNS([[NSMutableArray alloc] init]);
+    return self;
+}
+
+- (void)_webView:(WKWebView *)webView didFindTextManipulationItem:(_WKTextManipulationItem *)item
+{
+    [_items addObject:item];
+}
+
+- (NSArray<_WKTextManipulationItem *> *)items
+{
+    return _items.get();
+}
+
+@end
+
+namespace TestWebKitAPI {
+
+TEST(TextManipulation, StartTextManipulationFindSimpleParagraphs)
+{
+    auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    [webView _setTextManipulationDelegate:delegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html>"
+        "<html><body>hello<br>world<div>WebKit</div></body></html>"];
+
+    done = false;
+    [webView _startTextManipulationsWithCompletionHandler:^{
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    auto *items = [delegate items];
+    EXPECT_EQ(items.count, 3UL);
+    EXPECT_EQ(items[0].tokens.count, 1UL);
+    EXPECT_STREQ("hello", items[0].tokens[0].content.UTF8String);
+
+    EXPECT_EQ(items[1].tokens.count, 1UL);
+    EXPECT_STREQ("world", items[1].tokens[0].content.UTF8String);
+
+    EXPECT_EQ(items[2].tokens.count, 1UL);
+    EXPECT_STREQ("WebKit", items[2].tokens[0].content.UTF8String);
+}
+
+TEST(TextManipulation, StartTextManipulationFindMultipleParagraphsInSingleTextNode)
+{
+    auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    [webView _setTextManipulationDelegate:delegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html>"
+        "<html><body><pre>hello\nworld\nWebKit</pre></body></html>"];
+
+    done = false;
+    [webView _startTextManipulationsWithCompletionHandler:^{
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    auto *items = [delegate items];
+    EXPECT_EQ(items.count, 3UL);
+    EXPECT_EQ(items[0].tokens.count, 1UL);
+    EXPECT_STREQ("hello", items[0].tokens[0].content.UTF8String);
+
+    EXPECT_EQ(items[1].tokens.count, 1UL);
+    EXPECT_STREQ("world", items[1].tokens[0].content.UTF8String);
+
+    EXPECT_EQ(items[2].tokens.count, 1UL);
+    EXPECT_STREQ("WebKit", items[2].tokens[0].content.UTF8String);
+}
+
+TEST(TextManipulation, StartTextManipulationFindParagraphsWithMultileTokens)
+{
+    auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    [webView _setTextManipulationDelegate:delegate.get()];
+    
+    [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html>"
+        "<html><body>hello,  <b>world</b><br><div><em> <b>Web</b>Kit</em>  </div></body></html>"];
+    
+    done = false;
+    [webView _startTextManipulationsWithCompletionHandler:^{
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+    
+    auto *items = [delegate items];
+    EXPECT_EQ(items.count, 2UL);
+    EXPECT_EQ(items[0].tokens.count, 2UL);
+    EXPECT_STREQ("hello, ", items[0].tokens[0].content.UTF8String);
+    EXPECT_STREQ("world", items[0].tokens[1].content.UTF8String);
+    
+    EXPECT_EQ(items[1].tokens.count, 2UL);
+    EXPECT_STREQ("Web", items[1].tokens[0].content.UTF8String);
+    EXPECT_STREQ("Kit", items[1].tokens[1].content.UTF8String);
+}
+
+struct Token {
+    NSString *identifier;
+    NSString *content;
+};
+
+template<size_t length>
+static RetainPtr<_WKTextManipulationItem> createItem(NSString *itemIdentifier, const Token (&tokens)[length])
+{
+    RetainPtr<NSMutableArray> wkTokens = adoptNS([[NSMutableArray alloc] init]);
+    for (size_t i = 0; i < length; i++) {
+        RetainPtr<_WKTextManipulationToken> token = adoptNS([[_WKTextManipulationToken alloc] init]);
+        [token setIdentifier: tokens[i].identifier];
+        [token setContent: tokens[i].content];
+        [wkTokens addObject:token.get()];
+    }
+    return adoptNS([[_WKTextManipulationItem alloc] initWithIdentifier:itemIdentifier tokens:wkTokens.get()]);
+}
+
+TEST(TextManipulation, CompleteTextManipulationShouldReplaceSimpleParagraphContent)
+{
+    auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    [webView _setTextManipulationDelegate:delegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html>"
+        "<html><body><p>helllo, wooorld</p><p> hey, <b> Kits</b> is <em>cuuute</em></p></body></html>"];
+
+    done = false;
+    [webView _startTextManipulationsWithCompletionHandler:^{
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    auto *items = [delegate items];
+    EXPECT_EQ(items.count, 2UL);
+    EXPECT_EQ(items[0].tokens.count, 1UL);
+    EXPECT_STREQ("helllo, wooorld", items[0].tokens[0].content.UTF8String);
+
+    EXPECT_EQ(items[1].tokens.count, 4UL);
+    EXPECT_STREQ("hey, ", items[1].tokens[0].content.UTF8String);
+    EXPECT_STREQ("Kits", items[1].tokens[1].content.UTF8String);
+    EXPECT_STREQ(" is ", items[1].tokens[2].content.UTF8String);
+    EXPECT_STREQ("cuuute", items[1].tokens[3].content.UTF8String);
+
+    done = false;
+    [webView _completeTextManipulation:(_WKTextManipulationItem *)createItem(items[1].identifier, {
+        { items[1].tokens[0].identifier, @"Hello, " },
+        { items[1].tokens[1].identifier, @"kittens" },
+        { items[1].tokens[2].identifier, @" are " },
+        { items[1].tokens[3].identifier, @"cute" },
+    }).get() completion:^(BOOL success) {
+        EXPECT_TRUE(success);
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+    EXPECT_WK_STREQ("<p>helllo, wooorld</p><p>Hello, <b>kittens</b> are <em>cute</em></p>",
+        [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
+
+    done = false;
+    [webView _completeTextManipulation:(_WKTextManipulationItem *)createItem(items[0].identifier, {
+        { items[0].tokens[0].identifier, @"Hello, world." },
+    }).get() completion:^(BOOL success) {
+        EXPECT_TRUE(success);
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+    EXPECT_WK_STREQ("<p>Hello, world.</p><p>Hello, <b>kittens</b> are <em>cute</em></p>",
+        [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
+}
+
+TEST(TextManipulation, CompleteTextManipulationShouldFailWhenContentIsChanged)
+{
+    auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    [webView _setTextManipulationDelegate:delegate.get()];
+
+    [webView synchronouslyLoadHTMLString:@"<!DOCTYPE html>"
+        "<html><body><p> what <em>time</em> are they now?</p></body></html>"];
+
+    done = false;
+    [webView _startTextManipulationsWithCompletionHandler:^{
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    auto *items = [delegate items];
+    EXPECT_EQ(items.count, 1UL);
+    EXPECT_EQ(items[0].tokens.count, 3UL);
+    EXPECT_STREQ("what ", items[0].tokens[0].content.UTF8String);
+    EXPECT_STREQ("time", items[0].tokens[1].content.UTF8String);
+    EXPECT_STREQ(" are they now?", items[0].tokens[2].content.UTF8String);
+
+    [webView stringByEvaluatingJavaScript:@"document.querySelector('em').nextSibling.data = ' is it now in London?'"];
+
+    done = false;
+    [webView _completeTextManipulation:(_WKTextManipulationItem *)createItem(items[0].identifier, {
+        { items[0].tokens[0].identifier, @"What " },
+        { items[0].tokens[1].identifier, @"time" },
+        { items[0].tokens[2].identifier, @" is it now?" },
+    }).get() completion:^(BOOL success) {
+        EXPECT_FALSE(success);
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+    EXPECT_WK_STREQ("<p> what <em>time</em> is it now in London?</p>",
+        [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
+}
+
+TEST(TextManipulation, CompleteTextManipulationShouldFailWhenDocumentHasBeenNavigatedAway)
+{
+    auto delegate = adoptNS([[TextManipulationDelegate alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    [webView _setTextManipulationDelegate:delegate.get()];
+
+    [webView synchronouslyLoadTestPageNamed:@"simple"];
+    [webView stringByEvaluatingJavaScript:@"document.body.innerHTML = '<p>hey, <em>earth</em></p>'"];
+
+    done = false;
+    [webView _startTextManipulationsWithCompletionHandler:^{
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    auto *items = [delegate items];
+    EXPECT_EQ(items.count, 1UL);
+    EXPECT_EQ(items[0].tokens.count, 2UL);
+    EXPECT_STREQ("hey, ", items[0].tokens[0].content.UTF8String);
+    EXPECT_STREQ("earth", items[0].tokens[1].content.UTF8String);
+
+    [webView synchronouslyLoadTestPageNamed:@"copy-html"];
+    [webView stringByEvaluatingJavaScript:@"document.body.innerHTML = '<p>hey, <em>earth</em></p>'"];
+
+    done = false;
+    [webView _startTextManipulationsWithCompletionHandler:^{
+        done = true;
+    }];
+    TestWebKitAPI::Util::run(&done);
+
+    done = false;
+    [webView _completeTextManipulation:(_WKTextManipulationItem *)createItem(items[0].identifier, {
+        { items[0].tokens[0].identifier, @"hello, " },
+        { items[0].tokens[1].identifier, @"world" },
+    }).get() completion:^(BOOL success) {
+        EXPECT_FALSE(success);
+        done = true;
+    }];
+
+    TestWebKitAPI::Util::run(&done);
+}
+
+} // namespace TestWebKitAPI
+