Add injected bundle SPI to replace subresource URLs when dropping or pasting rich...
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 17 Jan 2018 17:07:43 +0000 (17:07 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 17 Jan 2018 17:07:43 +0000 (17:07 +0000)
https://bugs.webkit.org/show_bug.cgi?id=181637
<rdar://problem/36508471>

Reviewed by Tim Horton.

Source/WebCore:

Before carrying out blob URL conversion for pasted or dropped rich content, let the editor client replace
subresource URLs in WebKit2 by calling out to new injected bundle SPI. See comments below for more detail.

Tests:  WKAttachmentTests.InjectedBundleReplaceURLsWhenPastingAttributedString
        WKAttachmentTests.InjectedBundleReplaceURLWhenPastingImage

* editing/Editor.cpp:
(WebCore::Editor::clientReplacementURLForResource):
* editing/Editor.h:

Add a new helper to call out to the editor client for a URL string to replace a given ArchiveResource. In
WebKit2, this calls out to the injected bundle's new `replacementURLForResource` SPI hook.

* editing/cocoa/WebContentReaderCocoa.mm:
(WebCore::shouldReplaceSubresourceURL):
(WebCore::replaceRichContentWithAttachments):
(WebCore::replaceSubresourceURLsWithURLsFromClient):

Add a new static helper to replace subresource URLs in the given DocumentFragment with URLs supplied by the
editor client. Additionally builds a list of ArchiveResources that have not been replaced, for use at call sites
so that we don't unnecessarily create more Blobs for ArchiveResources that have already been replaced.

(WebCore::createFragmentAndAddResources):
(WebCore::sanitizeMarkupWithArchive):

Tweak web content reading codepaths to first replace subresource URLs with editor-client-supplied URLs.

(WebCore::WebContentReader::readImage):
(WebCore::shouldConvertToBlob): Deleted.

Rename this helper to shouldReplaceSubresourceURL, blob URL replacement is no longer the only scenario in which
we replace resource URLs, but in both cases, we still want to ignore `http:`-family and `data:` URLs.

* loader/EmptyClients.cpp:
* page/EditorClient.h:

Source/WebKit:

Add new injected bundle SPI, replacementURLForResource, which clients may use to provide a replacement URL to
represent an archive resource, given the resource's data and MIME type.

* WebProcess/InjectedBundle/API/APIInjectedBundleEditorClient.h:
(API::InjectedBundle::EditorClient::replacementURLForResource):
* WebProcess/InjectedBundle/API/Cocoa/WKWebProcessPlugInEditingDelegate.h:
* WebProcess/InjectedBundle/API/c/WKBundlePageEditorClient.h:

Add replacementURLForResource, and also bump the current injected bundle editor client version to 2.

* WebProcess/InjectedBundle/API/mac/WKWebProcessPlugInBrowserContextController.mm:
(-[WKWebProcessPlugInBrowserContextController _setEditingDelegate:]):
* WebProcess/InjectedBundle/InjectedBundlePageEditorClient.cpp:
(WebKit::InjectedBundlePageEditorClient::replacementURLForResource):
* WebProcess/InjectedBundle/InjectedBundlePageEditorClient.h:
* WebProcess/WebCoreSupport/WebEditorClient.cpp:
(WebKit::WebEditorClient::replacementURLForResource):
* WebProcess/WebCoreSupport/WebEditorClient.h:

Source/WebKitLegacy/mac:

Add a stub implementation of replacementURLForResource. See WebCore and WebKit ChangeLogs for more detail.

* WebCoreSupport/WebEditorClient.h:
* WebCoreSupport/WebEditorClient.mm:
(WebEditorClient::replacementURLForResource):

Source/WebKitLegacy/win:

Add a stub implementation of replacementURLForResource. See WebCore and WebKit ChangeLogs for more detail.

* WebCoreSupport/WebEditorClient.cpp:
(WebEditorClient::replacementURLForResource):
* WebCoreSupport/WebEditorClient.h:

Tools:

Add 2 new API tests to exercise injected bundle SPI for supplying replacement URLs when pasting an image, and an
attributed string containing multiple NSTextAttachments. See WebKit and WebCore ChangeLogs for more detail.

* TestWebKitAPI/Tests/WebKitCocoa/BundleEditingDelegatePlugIn.mm:
(-[BundleEditingDelegatePlugIn webProcessPlugIn:didCreateBrowserContextController:]):
(-[BundleEditingDelegatePlugIn _webProcessPlugInBrowserContextController:replacementURLForResource:mimeType:]):

Implement the new Objective-C bundle SPI to look up the incoming MIME type in the dictionary supplied via the
"MIMETypeToReplacementURLMap" bundle initialization parameter, and return it.

* TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm:
(webViewForTestingAttachments):
(-[TestWKWebView tagsInBody]):
(-[TestWKWebView expectElementTagsInOrder:]):
(-[TestWKWebView expectElementTag:toComeBefore:]):

Add a test helper to check that the given list of element tags appears in the document body. Also, reimplement
the existing -expectElementTag:toComeBefore: as a special case of -expectElementTagsInOrder:.

(TestWebKitAPI::TEST):
* WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp:
(WTR::InjectedBundlePage::InjectedBundlePage):

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

25 files changed:
Source/WebCore/ChangeLog
Source/WebCore/editing/Editor.cpp
Source/WebCore/editing/Editor.h
Source/WebCore/editing/cocoa/WebContentReaderCocoa.mm
Source/WebCore/loader/EmptyClients.cpp
Source/WebCore/page/EditorClient.h
Source/WebKit/ChangeLog
Source/WebKit/WebProcess/InjectedBundle/API/APIInjectedBundleEditorClient.h
Source/WebKit/WebProcess/InjectedBundle/API/Cocoa/WKWebProcessPlugInEditingDelegate.h
Source/WebKit/WebProcess/InjectedBundle/API/c/WKBundlePageEditorClient.h
Source/WebKit/WebProcess/InjectedBundle/API/mac/WKWebProcessPlugInBrowserContextController.mm
Source/WebKit/WebProcess/InjectedBundle/InjectedBundlePageEditorClient.cpp
Source/WebKit/WebProcess/InjectedBundle/InjectedBundlePageEditorClient.h
Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp
Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h
Source/WebKitLegacy/mac/ChangeLog
Source/WebKitLegacy/mac/WebCoreSupport/WebEditorClient.h
Source/WebKitLegacy/mac/WebCoreSupport/WebEditorClient.mm
Source/WebKitLegacy/win/ChangeLog
Source/WebKitLegacy/win/WebCoreSupport/WebEditorClient.cpp
Source/WebKitLegacy/win/WebCoreSupport/WebEditorClient.h
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKitCocoa/BundleEditingDelegatePlugIn.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm
Tools/WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp

index 20d6606..16e4e97 100644 (file)
@@ -1,3 +1,47 @@
+2018-01-17  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Add injected bundle SPI to replace subresource URLs when dropping or pasting rich content
+        https://bugs.webkit.org/show_bug.cgi?id=181637
+        <rdar://problem/36508471>
+
+        Reviewed by Tim Horton.
+
+        Before carrying out blob URL conversion for pasted or dropped rich content, let the editor client replace
+        subresource URLs in WebKit2 by calling out to new injected bundle SPI. See comments below for more detail.
+
+        Tests:  WKAttachmentTests.InjectedBundleReplaceURLsWhenPastingAttributedString
+                WKAttachmentTests.InjectedBundleReplaceURLWhenPastingImage
+
+        * editing/Editor.cpp:
+        (WebCore::Editor::clientReplacementURLForResource):
+        * editing/Editor.h:
+
+        Add a new helper to call out to the editor client for a URL string to replace a given ArchiveResource. In
+        WebKit2, this calls out to the injected bundle's new `replacementURLForResource` SPI hook.
+
+        * editing/cocoa/WebContentReaderCocoa.mm:
+        (WebCore::shouldReplaceSubresourceURL):
+        (WebCore::replaceRichContentWithAttachments):
+        (WebCore::replaceSubresourceURLsWithURLsFromClient):
+
+        Add a new static helper to replace subresource URLs in the given DocumentFragment with URLs supplied by the
+        editor client. Additionally builds a list of ArchiveResources that have not been replaced, for use at call sites
+        so that we don't unnecessarily create more Blobs for ArchiveResources that have already been replaced.
+
+        (WebCore::createFragmentAndAddResources):
+        (WebCore::sanitizeMarkupWithArchive):
+
+        Tweak web content reading codepaths to first replace subresource URLs with editor-client-supplied URLs.
+
+        (WebCore::WebContentReader::readImage):
+        (WebCore::shouldConvertToBlob): Deleted.
+
+        Rename this helper to shouldReplaceSubresourceURL, blob URL replacement is no longer the only scenario in which
+        we replace resource URLs, but in both cases, we still want to ignore `http:`-family and `data:` URLs.
+
+        * loader/EmptyClients.cpp:
+        * page/EditorClient.h:
+
 2018-01-17  Yacine Bandou  <yacine.bandou_ext@softathome.com>
         [EME][GStreamer] Add the full-sample encryption support in the GStreamer ClearKey decryptor
         https://bugs.webkit.org/show_bug.cgi?id=180080
index 31eeae3..feca4d1 100644 (file)
@@ -3961,4 +3961,12 @@ const Font* Editor::fontForSelection(bool& hasMultipleFonts) const
     return font;
 }
 
+String Editor::clientReplacementURLForResource(Ref<SharedBuffer>&& resourceData, const String& mimeType)
+{
+    if (auto* editorClient = client())
+        return editorClient->replacementURLForResource(WTFMove(resourceData), mimeType);
+
+    return { };
+}
+
 } // namespace WebCore
index e6a8967..0149fc2 100644 (file)
@@ -484,6 +484,8 @@ public:
     WEBCORE_EXPORT void replaceSelectionWithAttributedString(NSAttributedString *, MailBlockquoteHandling = MailBlockquoteHandling::RespectBlockquote);
 #endif
 
+    String clientReplacementURLForResource(Ref<SharedBuffer>&& resourceData, const String& mimeType);
+
 #if !PLATFORM(WIN)
     WEBCORE_EXPORT void writeSelectionToPasteboard(Pasteboard&);
     WEBCORE_EXPORT void writeImageToPasteboard(Pasteboard&, Element& imageElement, const URL&, const String& title);
index 1b9ae3f..03e18c0 100644 (file)
@@ -175,7 +175,7 @@ private:
 };
 
 
-static bool shouldConvertToBlob(const URL& url)
+static bool shouldReplaceSubresourceURL(const URL& url)
 {
     return !(url.protocolIsInHTTPFamily() || url.protocolIsData());
 }
@@ -223,7 +223,7 @@ static void replaceRichContentWithAttachments(DocumentFragment& fragment, const
     HashMap<AtomicString, Ref<Blob>> urlToBlobMap;
     for (const Ref<ArchiveResource>& subresource : subresources) {
         auto& url = subresource->url();
-        if (shouldConvertToBlob(url))
+        if (shouldReplaceSubresourceURL(url))
             urlToBlobMap.set(url.string(), Blob::create(subresource->data(), subresource->mimeType()));
     }
 
@@ -286,6 +286,30 @@ static void replaceRichContentWithAttachments(DocumentFragment& fragment, const
 #endif
 }
 
+static void replaceSubresourceURLsWithURLsFromClient(DocumentFragment& fragment, const Vector<Ref<ArchiveResource>>& subresources, Vector<Ref<ArchiveResource>>& outUnreplacedResources)
+{
+    ASSERT(fragment.document().frame());
+    auto& frame = *fragment.document().frame();
+    HashMap<AtomicString, AtomicString> subresourceURLToClientURLMap;
+    for (auto& subresource : subresources) {
+        auto& originalURL = subresource->url();
+        if (!shouldReplaceSubresourceURL(originalURL)) {
+            outUnreplacedResources.append(subresource.copyRef());
+            continue;
+        }
+
+        auto replacementURL = frame.editor().clientReplacementURLForResource(subresource->data(), subresource->mimeType());
+        if (replacementURL.isEmpty()) {
+            outUnreplacedResources.append(subresource.copyRef());
+            continue;
+        }
+
+        subresourceURLToClientURLMap.set(originalURL.string(), replacementURL);
+    }
+
+    if (!subresourceURLToClientURLMap.isEmpty())
+        replaceSubresourceURLs(fragment, WTFMove(subresourceURLToClientURLMap));
+}
 
 RefPtr<DocumentFragment> createFragmentAndAddResources(Frame& frame, NSAttributedString *string)
 {
@@ -309,13 +333,16 @@ RefPtr<DocumentFragment> createFragmentAndAddResources(Frame& frame, NSAttribute
         return WTFMove(fragmentAndResources.fragment);
     }
 
+    Vector<Ref<ArchiveResource>> unreplacedResources;
+    replaceSubresourceURLsWithURLsFromClient(*fragmentAndResources.fragment, fragmentAndResources.resources, unreplacedResources);
+
     if (shouldReplaceRichContentWithAttachments()) {
-        replaceRichContentWithAttachments(*fragmentAndResources.fragment, fragmentAndResources.resources);
+        replaceRichContentWithAttachments(*fragmentAndResources.fragment, unreplacedResources);
         return WTFMove(fragmentAndResources.fragment);
     }
 
     HashMap<AtomicString, AtomicString> blobURLMap;
-    for (const Ref<ArchiveResource>& subresource : fragmentAndResources.resources) {
+    for (const Ref<ArchiveResource>& subresource : unreplacedResources) {
         auto blob = Blob::create(subresource->data(), subresource->mimeType());
         String blobURL = DOMURL::createObjectURL(document, blob);
         blobURLMap.set(subresource->url().string(), blobURL);
@@ -366,15 +393,18 @@ static String sanitizeMarkupWithArchive(Document& destinationDocument, MarkupAnd
     ASSERT(stagingDocument);
     auto fragment = createFragmentFromMarkup(*stagingDocument, markupAndArchive.markup, markupAndArchive.mainResource->url(), DisallowScriptingAndPluginContent);
 
+    Vector<Ref<ArchiveResource>> unreplacedResources;
+    replaceSubresourceURLsWithURLsFromClient(fragment, markupAndArchive.archive->subresources(), unreplacedResources);
+
     if (shouldReplaceRichContentWithAttachments()) {
-        replaceRichContentWithAttachments(fragment, markupAndArchive.archive->subresources());
+        replaceRichContentWithAttachments(fragment, unreplacedResources);
         return markupForFragmentInDocument(WTFMove(fragment), *stagingDocument);
     }
 
     HashMap<AtomicString, AtomicString> blobURLMap;
-    for (const Ref<ArchiveResource>& subresource : markupAndArchive.archive->subresources()) {
+    for (const Ref<ArchiveResource>& subresource : unreplacedResources) {
         auto& subresourceURL = subresource->url();
-        if (!shouldConvertToBlob(subresourceURL))
+        if (!shouldReplaceSubresourceURL(subresourceURL))
             continue;
         auto blob = Blob::create(subresource->data(), subresource->mimeType());
         String blobURL = DOMURL::createObjectURL(destinationDocument, blob);
@@ -392,7 +422,7 @@ static String sanitizeMarkupWithArchive(Document& destinationDocument, MarkupAnd
             continue;
 
         auto subframeURL = subframeMainResource->url();
-        if (!shouldConvertToBlob(subframeURL))
+        if (!shouldReplaceSubresourceURL(subframeURL))
             continue;
 
         MarkupAndArchive subframeContent = { String::fromUTF8(subframeMainResource->data().data(), subframeMainResource->data().size()),
@@ -581,10 +611,16 @@ bool WebContentReader::readPlainText(const String& text)
 
 bool WebContentReader::readImage(Ref<SharedBuffer>&& buffer, const String& type)
 {
-    auto blob = Blob::create(buffer.get(), type);
     ASSERT(frame.document());
     auto& document = *frame.document();
 
+    auto replacementURL = frame.editor().clientReplacementURLForResource(buffer.copyRef(), type);
+    if (!replacementURL.isEmpty()) {
+        addFragment(createFragmentForImageAndURL(document, replacementURL));
+        return true;
+    }
+
+    auto blob = Blob::create(buffer.get(), type);
     if (shouldReplaceRichContentWithAttachments())
         addFragment(createFragmentForImageAttachment(document, WTFMove(blob)));
     else
index f24ad6a..25e9eb6 100644 (file)
@@ -176,6 +176,7 @@ private:
     void willWriteSelectionToPasteboard(Range*) final { }
     void didWriteSelectionToPasteboard() final { }
     void getClientPasteboardDataForRange(Range*, Vector<String>&, Vector<RefPtr<SharedBuffer>>&) final { }
+    String replacementURLForResource(Ref<SharedBuffer>&&, const String&) final { return { }; }
     void requestCandidatesForSelection(const VisibleSelection&) final { }
     void handleAcceptedCandidateWithSoftSpaces(TextCheckingResult) final { }
 
index 1f51a92..36ad7be 100644 (file)
@@ -86,6 +86,7 @@ public:
     virtual void willWriteSelectionToPasteboard(Range*) = 0;
     virtual void didWriteSelectionToPasteboard() = 0;
     virtual void getClientPasteboardDataForRange(Range*, Vector<String>& pasteboardTypes, Vector<RefPtr<SharedBuffer>>& pasteboardData) = 0;
+    virtual String replacementURLForResource(Ref<SharedBuffer>&& resourceData, const String& mimeType) = 0;
     virtual void requestCandidatesForSelection(const VisibleSelection&) { }
     virtual void handleAcceptedCandidateWithSoftSpaces(TextCheckingResult) { }
 
index 6beafde..2a38c8d 100644 (file)
@@ -1,3 +1,30 @@
+2018-01-17  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Add injected bundle SPI to replace subresource URLs when dropping or pasting rich content
+        https://bugs.webkit.org/show_bug.cgi?id=181637
+        <rdar://problem/36508471>
+
+        Reviewed by Tim Horton.
+
+        Add new injected bundle SPI, replacementURLForResource, which clients may use to provide a replacement URL to
+        represent an archive resource, given the resource's data and MIME type.
+
+        * WebProcess/InjectedBundle/API/APIInjectedBundleEditorClient.h:
+        (API::InjectedBundle::EditorClient::replacementURLForResource):
+        * WebProcess/InjectedBundle/API/Cocoa/WKWebProcessPlugInEditingDelegate.h:
+        * WebProcess/InjectedBundle/API/c/WKBundlePageEditorClient.h:
+
+        Add replacementURLForResource, and also bump the current injected bundle editor client version to 2.
+
+        * WebProcess/InjectedBundle/API/mac/WKWebProcessPlugInBrowserContextController.mm:
+        (-[WKWebProcessPlugInBrowserContextController _setEditingDelegate:]):
+        * WebProcess/InjectedBundle/InjectedBundlePageEditorClient.cpp:
+        (WebKit::InjectedBundlePageEditorClient::replacementURLForResource):
+        * WebProcess/InjectedBundle/InjectedBundlePageEditorClient.h:
+        * WebProcess/WebCoreSupport/WebEditorClient.cpp:
+        (WebKit::WebEditorClient::replacementURLForResource):
+        * WebProcess/WebCoreSupport/WebEditorClient.h:
+
 2018-01-17  Zan Dobersek  <zdobersek@igalia.com>
 
         [Cairo] Don't mirror global alpha and image interpolation quality state values in PlatformContextCairo
index 7774f75..6015e5f 100644 (file)
@@ -28,6 +28,7 @@
 #include <WebCore/EditorInsertAction.h>
 #include <WebCore/TextAffinity.h>
 #include <wtf/Forward.h>
+#include <wtf/text/WTFString.h>
 
 namespace WebCore {
 class CSSStyleDeclaration;
@@ -64,6 +65,7 @@ public:
     virtual void getPasteboardDataForRange(WebKit::WebPage&, WebCore::Range*, Vector<WTF::String>& pasteboardTypes, Vector<RefPtr<WebCore::SharedBuffer>>& pasteboardData) { }
     virtual void didWriteToPasteboard(WebKit::WebPage&) { }
     virtual bool performTwoStepDrop(WebKit::WebPage&, WebCore::DocumentFragment&, WebCore::Range&, bool) { return false; }
+    virtual WTF::String replacementURLForResource(WebKit::WebPage&, Ref<WebCore::SharedBuffer>&&, const WTF::String&) { return { }; }
 };
 
 } // namespace InjectedBundle
index c8209a1..9d1aebf 100644 (file)
@@ -61,6 +61,7 @@ WK_API_AVAILABLE(macosx(10.12.3), ios(10.3))
 - (NSDictionary<NSString *, NSData *> *)_webProcessPlugInBrowserContextController:(WKWebProcessPlugInBrowserContextController *)controller pasteboardDataForRange:(WKWebProcessPlugInRangeHandle *)range;
 - (void)_webProcessPlugInBrowserContextControllerDidWriteToPasteboard:(WKWebProcessPlugInBrowserContextController *)controller;
 - (BOOL)_webProcessPlugInBrowserContextController:(WKWebProcessPlugInBrowserContextController *)controller performTwoStepDrop:(WKWebProcessPlugInNodeHandle *)fragment atDestination:(WKWebProcessPlugInRangeHandle *)destination isMove:(BOOL)isMove WK_API_AVAILABLE(macosx(10.13), ios(11.0));
+- (NSString *)_webProcessPlugInBrowserContextController:(WKWebProcessPlugInBrowserContextController *)controller replacementURLForResource:(NSData *)resourceData mimeType:(NSString *)mimeType WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 @end
 
index 0b75650..df9e915 100644 (file)
@@ -62,6 +62,7 @@ typedef bool (*WKBundlePageShouldApplyStyle)(WKBundlePageRef page, WKBundleCSSSt
 typedef void (*WKBundlePageEditingNotification)(WKBundlePageRef page, WKStringRef notificationName, const void* clientInfo);
 typedef void (*WKBundlePageWillWriteToPasteboard)(WKBundlePageRef page, WKBundleRangeHandleRef range,  const void* clientInfo);
 typedef void (*WKBundlePageGetPasteboardDataForRange)(WKBundlePageRef page, WKBundleRangeHandleRef range, WKArrayRef* pasteboardTypes, WKArrayRef* pasteboardData, const void* clientInfo);
+typedef WKStringRef (*WKBundlePageReplacementURLForResource)(WKBundlePageRef, WKDataRef resourceData, WKStringRef mimeType, const void* clientInfo);
 typedef void (*WKBundlePageDidWriteToPasteboard)(WKBundlePageRef page, const void* clientInfo);
 typedef bool (*WKBundlePagePerformTwoStepDrop)(WKBundlePageRef page, WKBundleNodeHandleRef fragment, WKBundleRangeHandleRef destination, bool isMove, const void* clientInfo);
 
@@ -110,4 +111,30 @@ typedef struct WKBundlePageEditorClientV1 {
     WKBundlePagePerformTwoStepDrop                                      performTwoStepDrop;
 } WKBundlePageEditorClientV1;
 
+typedef struct WKBundlePageEditorClientV2 {
+    WKBundlePageEditorClientBase                                        base;
+
+    // Version 0.
+    WKBundlePageShouldBeginEditingCallback                              shouldBeginEditing;
+    WKBundlePageShouldEndEditingCallback                                shouldEndEditing;
+    WKBundlePageShouldInsertNodeCallback                                shouldInsertNode;
+    WKBundlePageShouldInsertTextCallback                                shouldInsertText;
+    WKBundlePageShouldDeleteRangeCallback                               shouldDeleteRange;
+    WKBundlePageShouldChangeSelectedRange                               shouldChangeSelectedRange;
+    WKBundlePageShouldApplyStyle                                        shouldApplyStyle;
+    WKBundlePageEditingNotification                                     didBeginEditing;
+    WKBundlePageEditingNotification                                     didEndEditing;
+    WKBundlePageEditingNotification                                     didChange;
+    WKBundlePageEditingNotification                                     didChangeSelection;
+
+    // Version 1.
+    WKBundlePageWillWriteToPasteboard                                   willWriteToPasteboard;
+    WKBundlePageGetPasteboardDataForRange                               getPasteboardDataForRange;
+    WKBundlePageDidWriteToPasteboard                                    didWriteToPasteboard;
+    WKBundlePagePerformTwoStepDrop                                      performTwoStepDrop;
+
+    // Version 2.
+    WKBundlePageReplacementURLForResource                               replacementURLForResource;
+} WKBundlePageEditorClientV2;
+
 #endif // WKBundlePageEditorClient_h
index f2d94a9..3399efe 100644 (file)
@@ -678,6 +678,16 @@ static inline WKEditorInsertAction toWK(EditorInsertAction action)
             return [m_controller->_editingDelegate.get() _webProcessPlugInBrowserContextController:m_controller performTwoStepDrop:wrapper(*nodeHandle) atDestination:wrapper(*rangeHandle) isMove:isMove];
         }
 
+        WTF::String replacementURLForResource(WebKit::WebPage&, Ref<WebCore::SharedBuffer>&& resourceData, const WTF::String& mimeType)
+        {
+            if (!m_delegateMethods.replacementURLForResource)
+                return { };
+
+            NSString *type = (NSString *)mimeType;
+            auto data = resourceData->createNSData();
+            return [m_controller->_editingDelegate.get() _webProcessPlugInBrowserContextController:m_controller replacementURLForResource:data.get() mimeType:type];
+        }
+
         WKWebProcessPlugInBrowserContextController *m_controller;
         const struct DelegateMethods {
             DelegateMethods(RetainPtr<id <WKWebProcessPlugInEditingDelegate>> delegate)
@@ -688,6 +698,7 @@ static inline WKEditorInsertAction toWK(EditorInsertAction action)
                 , getPasteboardDataForRange([delegate respondsToSelector:@selector(_webProcessPlugInBrowserContextController:pasteboardDataForRange:)])
                 , didWriteToPasteboard([delegate respondsToSelector:@selector(_webProcessPlugInBrowserContextControllerDidWriteToPasteboard:)])
                 , performTwoStepDrop([delegate respondsToSelector:@selector(_webProcessPlugInBrowserContextController:performTwoStepDrop:atDestination:isMove:)])
+                , replacementURLForResource([delegate respondsToSelector:@selector(_webProcessPlugInBrowserContextController:replacementURLForResource:mimeType:)])
             {
             }
 
@@ -698,6 +709,7 @@ static inline WKEditorInsertAction toWK(EditorInsertAction action)
             bool getPasteboardDataForRange;
             bool didWriteToPasteboard;
             bool performTwoStepDrop;
+            bool replacementURLForResource;
         } m_delegateMethods;
     };
 
index 0493d5c..bcf9e84 100644 (file)
@@ -33,6 +33,8 @@
 #include "InjectedBundleRangeHandle.h"
 #include "WKAPICast.h"
 #include "WKBundleAPICast.h"
+#include "WKData.h"
+#include "WKRetainPtr.h"
 #include "WKString.h"
 #include "WebPage.h"
 #include <WebCore/DocumentFragment.h>
@@ -189,4 +191,14 @@ void InjectedBundlePageEditorClient::didWriteToPasteboard(WebPage& page)
         m_client.didWriteToPasteboard(toAPI(&page), m_client.base.clientInfo);
 }
 
+String InjectedBundlePageEditorClient::replacementURLForResource(WebPage& page, Ref<SharedBuffer>&& resourceData, const String& mimeType)
+{
+    if (!m_client.replacementURLForResource)
+        return { };
+
+    auto data = adoptWK(WKDataCreate(reinterpret_cast<const unsigned char*>(resourceData->data()), resourceData->size()));
+    auto type = adoptWK(toCopiedAPI(mimeType));
+    return toWTFString(m_client.replacementURLForResource(toAPI(&page), data.get(), type.get(), m_client.base.clientInfo));
+}
+
 } // namespace WebKit
index 689941a..1d976b6 100644 (file)
@@ -31,7 +31,7 @@
 
 namespace API {
 template<> struct ClientTraits<WKBundlePageEditorClientBase> {
-    typedef std::tuple<WKBundlePageEditorClientV0, WKBundlePageEditorClientV1> Versions;
+    typedef std::tuple<WKBundlePageEditorClientV0, WKBundlePageEditorClientV1, WKBundlePageEditorClientV2> Versions;
 };
 }
 
@@ -67,6 +67,7 @@ private:
     void getPasteboardDataForRange(WebPage&, WebCore::Range*, Vector<String>& pasteboardTypes, Vector<RefPtr<WebCore::SharedBuffer>>& pasteboardData) final;
     void didWriteToPasteboard(WebPage&) final;
     bool performTwoStepDrop(WebPage&, WebCore::DocumentFragment&, WebCore::Range& destination, bool isMove) final;
+    String replacementURLForResource(WebPage&, Ref<WebCore::SharedBuffer>&& resourceData, const String&) final;
 };
 
 } // namespace WebKit
index 6c69d09..884ceb6 100644 (file)
@@ -263,6 +263,11 @@ bool WebEditorClient::performTwoStepDrop(DocumentFragment& fragment, Range& dest
     return m_page->injectedBundleEditorClient().performTwoStepDrop(*m_page, fragment, destination, isMove);
 }
 
+String WebEditorClient::replacementURLForResource(Ref<WebCore::SharedBuffer>&& resourceData, const String& mimeType)
+{
+    return m_page->injectedBundleEditorClient().replacementURLForResource(*m_page, WTFMove(resourceData), mimeType);
+}
+
 void WebEditorClient::registerUndoStep(UndoStep& step)
 {
     // FIXME: Add assertion that the command being reapplied is the same command that is
index fde710a..be66faf 100644 (file)
@@ -76,6 +76,7 @@ private:
     void willWriteSelectionToPasteboard(WebCore::Range*) final;
     void didWriteSelectionToPasteboard() final;
     void getClientPasteboardDataForRange(WebCore::Range*, Vector<String>& pasteboardTypes, Vector<RefPtr<WebCore::SharedBuffer>>& pasteboardData) final;
+    String replacementURLForResource(Ref<WebCore::SharedBuffer>&& resourceData, const String& mimeType) final;
     
     void registerUndoStep(WebCore::UndoStep&) final;
     void registerRedoStep(WebCore::UndoStep&) final;
index e28fb33..f18ecd3 100644 (file)
@@ -1,3 +1,17 @@
+2018-01-17  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Add injected bundle SPI to replace subresource URLs when dropping or pasting rich content
+        https://bugs.webkit.org/show_bug.cgi?id=181637
+        <rdar://problem/36508471>
+
+        Reviewed by Tim Horton.
+
+        Add a stub implementation of replacementURLForResource. See WebCore and WebKit ChangeLogs for more detail.
+
+        * WebCoreSupport/WebEditorClient.h:
+        * WebCoreSupport/WebEditorClient.mm:
+        (WebEditorClient::replacementURLForResource):
+
 2018-01-11  Keith Miller  <keith_miller@apple.com>
 
         Rename ENABLE_ASYNC_ITERATION to ENABLE_JS_ASYNC_ITERATION
index 4511947..fa2ed32 100644 (file)
@@ -78,6 +78,7 @@ private:
     void willWriteSelectionToPasteboard(WebCore::Range*) final;
     void didWriteSelectionToPasteboard() final;
     void getClientPasteboardDataForRange(WebCore::Range*, Vector<String>& pasteboardTypes, Vector<RefPtr<WebCore::SharedBuffer>>& pasteboardData) final;
+    String replacementURLForResource(Ref<WebCore::SharedBuffer>&& resourceData, const String& mimeType) final;
 
     void setInsertionPasteboard(const String&) final;
 
index e807a3e..041baca 100644 (file)
@@ -419,6 +419,12 @@ void WebEditorClient::getClientPasteboardDataForRange(WebCore::Range*, Vector<St
     // Not implemented WebKit, only WebKit2.
 }
 
+String WebEditorClient::replacementURLForResource(Ref<SharedBuffer>&&, const String&)
+{
+    // Not implemented in WebKitLegacy.
+    return { };
+}
+
 #if (PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000) || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300)
 
 // FIXME: Remove both this stub and the real version of this function below once we don't need the real version on any supported platform.
index 9f9c309..ea59cab 100644 (file)
@@ -1,3 +1,17 @@
+2018-01-17  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Add injected bundle SPI to replace subresource URLs when dropping or pasting rich content
+        https://bugs.webkit.org/show_bug.cgi?id=181637
+        <rdar://problem/36508471>
+
+        Reviewed by Tim Horton.
+
+        Add a stub implementation of replacementURLForResource. See WebCore and WebKit ChangeLogs for more detail.
+
+        * WebCoreSupport/WebEditorClient.cpp:
+        (WebEditorClient::replacementURLForResource):
+        * WebCoreSupport/WebEditorClient.h:
+
 2017-12-28  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         Remove std::chrono completely
index 5b0b5cb..56e7cf0 100644 (file)
@@ -269,6 +269,12 @@ void WebEditorClient::getClientPasteboardDataForRange(WebCore::Range*, Vector<St
     notImplemented();
 }
 
+String WebEditorClient::replacementURLForResource(Ref<WebCore::SharedBuffer>&&, const String&)
+{
+    notImplemented();
+    return { };
+}
+
 bool WebEditorClient::shouldDeleteRange(Range* range)
 {
     COMPtr<IWebEditingDelegate> ed;
index 63ec4fc..7a6edde 100644 (file)
@@ -54,6 +54,7 @@ private:
     void willWriteSelectionToPasteboard(WebCore::Range*) final;
     void didWriteSelectionToPasteboard() final;
     void getClientPasteboardDataForRange(WebCore::Range*, Vector<String>& pasteboardTypes, Vector<RefPtr<WebCore::SharedBuffer>>& pasteboardData) final;
+    String replacementURLForResource(Ref<WebCore::SharedBuffer>&&, const String&) final;
 
     void didEndUserTriggeredSelectionChanges() final { }
     void respondToChangedContents() final;
index 3c4b06b..2f9fd15 100644 (file)
@@ -1,3 +1,34 @@
+2018-01-17  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Add injected bundle SPI to replace subresource URLs when dropping or pasting rich content
+        https://bugs.webkit.org/show_bug.cgi?id=181637
+        <rdar://problem/36508471>
+
+        Reviewed by Tim Horton.
+
+        Add 2 new API tests to exercise injected bundle SPI for supplying replacement URLs when pasting an image, and an
+        attributed string containing multiple NSTextAttachments. See WebKit and WebCore ChangeLogs for more detail.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/BundleEditingDelegatePlugIn.mm:
+        (-[BundleEditingDelegatePlugIn webProcessPlugIn:didCreateBrowserContextController:]):
+        (-[BundleEditingDelegatePlugIn _webProcessPlugInBrowserContextController:replacementURLForResource:mimeType:]):
+
+        Implement the new Objective-C bundle SPI to look up the incoming MIME type in the dictionary supplied via the
+        "MIMETypeToReplacementURLMap" bundle initialization parameter, and return it.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm:
+        (webViewForTestingAttachments):
+        (-[TestWKWebView tagsInBody]):
+        (-[TestWKWebView expectElementTagsInOrder:]):
+        (-[TestWKWebView expectElementTag:toComeBefore:]):
+
+        Add a test helper to check that the given list of element tags appears in the document body. Also, reimplement
+        the existing -expectElementTag:toComeBefore: as a special case of -expectElementTagsInOrder:.
+
+        (TestWebKitAPI::TEST):
+        * WebKitTestRunner/InjectedBundle/InjectedBundlePage.cpp:
+        (WTR::InjectedBundlePage::InjectedBundlePage):
+
 2018-01-17  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         WebDriver: add support for test expectations
index c63c0a8..33f2bb8 100644 (file)
@@ -47,6 +47,7 @@
     RetainPtr<id <BundleEditingDelegateProtocol>> _remoteObject;
     BOOL _editingDelegateShouldInsertText;
     BOOL _shouldOverridePerformTwoStepDrop;
+    RetainPtr<NSDictionary<NSString *, NSString *>> _mimeTypeToReplacementURLMap;
 }
 
 - (void)webProcessPlugIn:(WKWebProcessPlugInController *)plugInController didCreateBrowserContextController:(WKWebProcessPlugInBrowserContextController *)browserContextController
@@ -63,6 +64,7 @@
         _editingDelegateShouldInsertText = YES;
 
     _shouldOverridePerformTwoStepDrop = [[plugInController.parameters valueForKey:@"BundleOverridePerformTwoStepDrop"] boolValue];
+    _mimeTypeToReplacementURLMap = [plugInController.parameters valueForKey:@"MIMETypeToReplacementURLMap"];
 
     _WKRemoteObjectInterface *interface = [_WKRemoteObjectInterface remoteObjectInterfaceWithProtocol:@protocol(BundleEditingDelegateProtocol)];
     _remoteObject = [browserContextController._remoteObjectRegistry remoteObjectProxyWithInterface:interface];
     return _shouldOverridePerformTwoStepDrop;
 }
 
+- (NSString *)_webProcessPlugInBrowserContextController:(WKWebProcessPlugInBrowserContextController *)controller replacementURLForResource:(NSData *)resourceData mimeType:(NSString *)mimeType
+{
+    assert(!!resourceData);
+    return [_mimeTypeToReplacementURLMap objectForKey:mimeType];
+}
+
 @end
 
 #endif // WK_API_ENABLED
index b87a60b..b9a32c9 100644 (file)
@@ -28,6 +28,7 @@
 #import "DataInteractionSimulator.h"
 #import "PlatformUtilities.h"
 #import "TestWKWebView.h"
+#import "WKWebViewConfigurationExtras.h"
 #import <WebKit/WKPreferencesRefPrivate.h>
 #import <WebKit/WKWebViewPrivate.h>
 #import <WebKit/WebKit.h>
@@ -144,18 +145,23 @@ private:
 @interface TestWKWebView (AttachmentTesting)
 @end
 
-static RetainPtr<TestWKWebView> webViewForTestingAttachments(CGSize webViewSize)
+static RetainPtr<TestWKWebView> webViewForTestingAttachments(CGSize webViewSize, WKWebViewConfiguration *configuration)
 {
-    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
-    [configuration _setAttachmentElementEnabled:YES];
+    configuration._attachmentElementEnabled = YES;
     WKPreferencesSetCustomPasteboardDataEnabled((WKPreferencesRef)[configuration preferences], YES);
 
-    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, webViewSize.width, webViewSize.height) configuration:configuration.get()]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, webViewSize.width, webViewSize.height) configuration:configuration]);
     [webView synchronouslyLoadHTMLString:@"<meta name='viewport' content='width=device-width, initial-scale=1'><script>focus = () => document.body.focus()</script><body onload=focus() contenteditable></body>"];
 
     return webView;
 }
 
+static RetainPtr<TestWKWebView> webViewForTestingAttachments(CGSize webViewSize)
+{
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    return webViewForTestingAttachments(webViewSize, configuration.get());
+}
+
 static RetainPtr<TestWKWebView> webViewForTestingAttachments()
 {
     return webViewForTestingAttachments(CGSizeMake(500, 500));
@@ -207,20 +213,29 @@ static _WKAttachmentDisplayOptions *displayOptionsWithMode(_WKAttachmentDisplayM
 
 @implementation TestWKWebView (AttachmentTesting)
 
-- (void)expectElementTag:(NSString *)tagName toComeBefore:(NSString *)otherTagName
+- (NSArray<NSString *> *)tagsInBody
+{
+    return [self objectByEvaluatingJavaScript:@"Array.from(document.body.getElementsByTagName('*')).map(e => e.tagName)"];
+}
+
+- (void)expectElementTagsInOrder:(NSArray<NSString *> *)tagNames
 {
-    NSArray *tagsInBody = [self objectByEvaluatingJavaScript:@"Array.from(document.body.getElementsByTagName('*')).map(e => e.tagName)"];
-    BOOL success = [tagsInBody containsObject:tagName] && [tagsInBody containsObject:otherTagName];
-    if (success) {
-        NSUInteger index = [tagsInBody indexOfObject:tagName];
-        NSUInteger otherIndex = [tagsInBody indexOfObjectWithOptions:NSEnumerationReverse passingTest:[&] (NSString *tag, NSUInteger, BOOL *) {
-            return [tag isEqualToString:otherTagName];
-        }];
-        success = index < otherIndex;
+    auto remainingTags = adoptNS([tagNames mutableCopy]);
+    NSArray<NSString *> *tagsInBody = self.tagsInBody;
+    for (NSString *tag in tagsInBody.reverseObjectEnumerator) {
+        if ([tag isEqualToString:[remainingTags lastObject]])
+            [remainingTags removeLastObject];
+        if (![remainingTags count])
+            break;
     }
-    EXPECT_TRUE(success);
-    if (!success)
-        NSLog(@"Expected %@ to come before %@ in tags: %@", tagName, otherTagName, tagsInBody);
+    EXPECT_EQ([remainingTags count], 0U);
+    if ([remainingTags count])
+        NSLog(@"Expected to find ordered tags: %@ in: %@", tagNames, tagsInBody);
+}
+
+- (void)expectElementTag:(NSString *)tagName toComeBefore:(NSString *)otherTagName
+{
+    [self expectElementTagsInOrder:@[tagName, otherTagName]];
 }
 
 - (BOOL)_synchronouslyExecuteEditCommand:(NSString *)command argument:(NSString *)argument
@@ -1096,6 +1111,38 @@ TEST(WKAttachmentTests, InsertDuplicateAttachmentAndUpdateData)
     [pastedAttachment expectRequestedDataToBe:originalData.get()];
 }
 
+TEST(WKAttachmentTests, InjectedBundleReplaceURLsWhenPastingAttributedString)
+{
+    platformCopyRichTextWithMultipleAttachments();
+
+    auto configuration = retainPtr([WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"BundleEditingDelegatePlugIn"]);
+    [[configuration processPool] _setObject:@{ @"image/png" : @"cid:foo-bar" } forBundleParameter:@"MIMETypeToReplacementURLMap"];
+    auto webView = webViewForTestingAttachments(CGSizeMake(500, 500), configuration.get());
+    {
+        ObserveAttachmentUpdatesForScope observer(webView.get());
+        [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
+        EXPECT_EQ(2U, observer.observer().inserted.count);
+    }
+    [webView expectElementTagsInOrder:@[ @"IMG", @"ATTACHMENT", @"ATTACHMENT" ]];
+    EXPECT_WK_STREQ("cid:foo-bar", [webView valueOfAttribute:@"src" forQuerySelector:@"img"]);
+}
+
+TEST(WKAttachmentTests, InjectedBundleReplaceURLWhenPastingImage)
+{
+    platformCopyPNG();
+
+    NSString *replacementURL = @"cid:foo-bar";
+    auto configuration = retainPtr([WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"BundleEditingDelegatePlugIn"]);
+    [[configuration processPool] _setObject:@{ @"image/tiff" : replacementURL, @"image/png" : replacementURL } forBundleParameter:@"MIMETypeToReplacementURLMap"];
+    auto webView = webViewForTestingAttachments(CGSizeMake(500, 500), configuration.get());
+    {
+        ObserveAttachmentUpdatesForScope observer(webView.get());
+        [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
+        EXPECT_EQ(0U, observer.observer().inserted.count);
+    }
+    EXPECT_WK_STREQ("cid:foo-bar", [webView valueOfAttribute:@"src" forQuerySelector:@"img"]);
+}
+
 #pragma mark - Platform-specific tests
 
 #if PLATFORM(MAC)
index dd64841..9933d81 100644 (file)
@@ -358,8 +358,8 @@ InjectedBundlePage::InjectedBundlePage(WKBundlePageRef page)
     };
     WKBundlePageSetUIClient(m_page, &uiClient.base);
 
-    WKBundlePageEditorClientV1 editorClient = {
-        { 1, this },
+    WKBundlePageEditorClientV2 editorClient = {
+        { 2, this },
         shouldBeginEditing,
         shouldEndEditing,
         shouldInsertNode,
@@ -375,6 +375,7 @@ InjectedBundlePage::InjectedBundlePage(WKBundlePageRef page)
         0, /* getPasteboardDataForRange */
         0, /* didWriteToPasteboard */
         0, /* performTwoStepDrop */
+        0, /* replacementURLForResource */
     };
     WKBundlePageSetEditorClient(m_page, &editorClient.base);