[Cocoa] Attachment dropped from one web view to another is missing its file wrapper
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 31 Oct 2018 05:29:17 +0000 (05:29 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 31 Oct 2018 05:29:17 +0000 (05:29 +0000)
https://bugs.webkit.org/show_bug.cgi?id=190530
<rdar://problem/45232149>

Reviewed by Tim Horton.

Source/WebCore:

Add support for copying and pasting attachment elements across web views by encoding and adding file wrapper
data as subresources in the web archive when writing selected web content to the pasteboard, and then decoding
and creating NSFileWrappers upon reading web content.

Test: WKAttachmentTests.CopyAndPasteBetweenWebViews

* WebCore.xcodeproj/project.pbxproj:
* editing/Editor.cpp:
(WebCore::Editor::registerAttachments):
* editing/Editor.h:

Add registerAttachments(), which registers _WKAttachments in the UI process given a list of
SerializedAttachmentData. This behaves similarly to registerAttachmentIdentifiers(), but differs in that (1) it
sends serialized file wrapper data, and (2) it sends a list of serialized attachments, rather than information
about just a single attachment.

* editing/SerializedAttachmentData.h:

Introduce SerializedAttachmentData, a struct containing information needed to serialize and deserialize an
attachment. These are used both when writing attachment data to the pasteboard, and when consuming attachment
data upon paste.

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

Add a step when pasting rich content with attachments, to collect and send serialized attachments to the client.
Also, drive-by fix: don't WTFMove() the Ref here if it's still going to be used below.

* html/HTMLAttachmentElement.cpp:
(WebCore::HTMLAttachmentElement::archiveResourceURL):
* html/HTMLAttachmentElement.h:

Add a static helper function to compute a URL that represents the data for the given attachment identifier, for
use in a web archive resource.

* loader/archive/cf/LegacyWebArchive.cpp:
(WebCore::addSubresourcesForAttachmentElementsIfNecessary):

Add a helper function to create and append ArchiveResources representing attachment element data when writing
attachments to the pasteboard via web archive data.

(WebCore::LegacyWebArchive::create):
* page/EditorClient.h:
(WebCore::EditorClient::registerAttachments):
(WebCore::EditorClient::serializedAttachmentDataForIdentifiers):

Source/WebKit:

See WebCore ChangeLog for more details.

* Shared/WebCoreArgumentCoders.cpp:
(IPC::ArgumentCoder<SerializedAttachmentData>::encode):
(IPC::ArgumentCoder<SerializedAttachmentData>::decode):
* Shared/WebCoreArgumentCoders.h:

Add IPC encoding/decoding support for SerializedAttachmentData.

* UIProcess/API/APIAttachment.cpp:
(API::Attachment::createSerializedRepresentation const):
(API::Attachment::updateFromSerializedRepresentation):
* UIProcess/API/APIAttachment.h:
* UIProcess/API/Cocoa/APIAttachmentCocoa.mm:
(API::Attachment::createSerializedRepresentation const):

Add a method to serialize and return attachment info as a blob of data.

(API::Attachment::updateFromSerializedRepresentation):

Add a method to update the attachment, given a serialized blob of data. On Cocoa platforms, this fails
gracefully if the serialized data cannot be decoded.

* UIProcess/Cocoa/PageClientImplCocoa.h:
* UIProcess/Cocoa/PageClientImplCocoa.mm:
(WebKit::PageClientImplCocoa::allocFileWrapperInstance const):
(WebKit::PageClientImplCocoa::serializableFileWrapperClasses const):
(WebKit::PageClientImplCocoa::allocFileWrapperInstance): Deleted.

Add an additional hook to return the list of NSFileWrapper subclasses suitable for deserialization. This
array contains (at minimum) NSFileWrapper, but may additionally include a custom NSFileWrapper subclass, if
configured.

* UIProcess/PageClient.h:
(WebKit::PageClient::allocFileWrapperInstance const):
(WebKit::PageClient::serializableFileWrapperClasses const):
(WebKit::PageClient::allocFileWrapperInstance): Deleted.
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::registerAttachmentsFromSerializedData):

Registers the given serialized attachment data, creating new _WKAttachment instances if necessary. Currently,
this does not update the file wrappers of existing _WKAttachments; we should revisit this in the future to see
if we can additionally update file wrappers for existing attachments, without breaking the case where the user
copies and pastes or drags and drops attachments within a single web view and the client expects _WKAttachment
instances to be reused.

(WebKit::WebPageProxy::serializedAttachmentDataForIdentifiers):
* UIProcess/WebPageProxy.h:
* UIProcess/WebPageProxy.messages.in:
* WebProcess/WebCoreSupport/WebEditorClient.cpp:
(WebKit::WebEditorClient::registerAttachments):
(WebKit::WebEditorClient::serializedAttachmentDataForIdentifiers):

Implement a new IPC hook to fetch an array of serialized attachment data blobs, given a list of attachment
identifiers.

* WebProcess/WebCoreSupport/WebEditorClient.h:

Tools:

Add a test to verify that copying different types of attachments and pasting in a new web view inserts
attachments in the second web view that are backed by _WKAttachment objects, whose NSFileWrappers hold data that
is equivalent to the original file wrappers used to insert attachments in the first web view.

Existing API tests verify that when copying and pasting within a single web view, the pasted attachment element
is still backed by the same NSFileWrapper instance.

* TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm:
(TestWebKitAPI::TEST):

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

26 files changed:
Source/WebCore/ChangeLog
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/editing/Editor.cpp
Source/WebCore/editing/Editor.h
Source/WebCore/editing/SerializedAttachmentData.h [new file with mode: 0644]
Source/WebCore/editing/cocoa/WebContentReaderCocoa.mm
Source/WebCore/html/HTMLAttachmentElement.cpp
Source/WebCore/html/HTMLAttachmentElement.h
Source/WebCore/loader/archive/cf/LegacyWebArchive.cpp
Source/WebCore/page/EditorClient.h
Source/WebKit/ChangeLog
Source/WebKit/Shared/WebCoreArgumentCoders.cpp
Source/WebKit/Shared/WebCoreArgumentCoders.h
Source/WebKit/UIProcess/API/APIAttachment.cpp
Source/WebKit/UIProcess/API/APIAttachment.h
Source/WebKit/UIProcess/API/Cocoa/APIAttachmentCocoa.mm
Source/WebKit/UIProcess/Cocoa/PageClientImplCocoa.h
Source/WebKit/UIProcess/Cocoa/PageClientImplCocoa.mm
Source/WebKit/UIProcess/PageClient.h
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/WebPageProxy.messages.in
Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp
Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm

index 2fe16e2..1ca4df2 100644 (file)
@@ -1,3 +1,57 @@
+2018-10-30  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [Cocoa] Attachment dropped from one web view to another is missing its file wrapper
+        https://bugs.webkit.org/show_bug.cgi?id=190530
+        <rdar://problem/45232149>
+
+        Reviewed by Tim Horton.
+
+        Add support for copying and pasting attachment elements across web views by encoding and adding file wrapper
+        data as subresources in the web archive when writing selected web content to the pasteboard, and then decoding
+        and creating NSFileWrappers upon reading web content.
+
+        Test: WKAttachmentTests.CopyAndPasteBetweenWebViews
+
+        * WebCore.xcodeproj/project.pbxproj:
+        * editing/Editor.cpp:
+        (WebCore::Editor::registerAttachments):
+        * editing/Editor.h:
+
+        Add registerAttachments(), which registers _WKAttachments in the UI process given a list of
+        SerializedAttachmentData. This behaves similarly to registerAttachmentIdentifiers(), but differs in that (1) it
+        sends serialized file wrapper data, and (2) it sends a list of serialized attachments, rather than information
+        about just a single attachment.
+
+        * editing/SerializedAttachmentData.h:
+
+        Introduce SerializedAttachmentData, a struct containing information needed to serialize and deserialize an
+        attachment. These are used both when writing attachment data to the pasteboard, and when consuming attachment
+        data upon paste.
+
+        * editing/cocoa/WebContentReaderCocoa.mm:
+        (WebCore::replaceRichContentWithAttachments):
+
+        Add a step when pasting rich content with attachments, to collect and send serialized attachments to the client.
+        Also, drive-by fix: don't WTFMove() the Ref here if it's still going to be used below.
+
+        * html/HTMLAttachmentElement.cpp:
+        (WebCore::HTMLAttachmentElement::archiveResourceURL):
+        * html/HTMLAttachmentElement.h:
+
+        Add a static helper function to compute a URL that represents the data for the given attachment identifier, for
+        use in a web archive resource.
+
+        * loader/archive/cf/LegacyWebArchive.cpp:
+        (WebCore::addSubresourcesForAttachmentElementsIfNecessary):
+
+        Add a helper function to create and append ArchiveResources representing attachment element data when writing
+        attachments to the pasteboard via web archive data.
+
+        (WebCore::LegacyWebArchive::create):
+        * page/EditorClient.h:
+        (WebCore::EditorClient::registerAttachments):
+        (WebCore::EditorClient::serializedAttachmentDataForIdentifiers):
+
 2018-10-30  David Kilzer  <ddkilzer@apple.com>
 
         XSLTProcessor should limit max transform depth
index 21d25fd..c7850e3 100644 (file)
                F49786881FF45FA500E060AB /* PasteboardItemInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = F49786871FF45FA500E060AB /* PasteboardItemInfo.h */; settings = {ATTRIBUTES = (Private, ); }; };
                F4BFB9851E1DDF9B00862C24 /* DumpEditingHistory.js in Copy Scripts */ = {isa = PBXBuildFile; fileRef = F48389831E1DDF2B0076B7EA /* DumpEditingHistory.js */; };
                F4BFB9861E1DDF9B00862C24 /* EditingHistoryUtil.js in Copy Scripts */ = {isa = PBXBuildFile; fileRef = F48389841E1DDF2B0076B7EA /* EditingHistoryUtil.js */; };
+               F4D43D662188038B00ECECAC /* SerializedAttachmentData.h in Headers */ = {isa = PBXBuildFile; fileRef = F4D43D64218802E600ECECAC /* SerializedAttachmentData.h */; settings = {ATTRIBUTES = (Private, ); }; };
                F4E57EDC213F3F5F004EA98E /* FontAttributeChanges.h in Headers */ = {isa = PBXBuildFile; fileRef = F4E57EDA213F3F5F004EA98E /* FontAttributeChanges.h */; settings = {ATTRIBUTES = (Private, ); }; };
                F4E57EE1213F434A004EA98E /* WebCoreNSFontManagerExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = F4E57EDF213F434A004EA98E /* WebCoreNSFontManagerExtras.h */; settings = {ATTRIBUTES = (Private, ); }; };
                F50664F8157F52DC00AC226F /* FormController.h in Headers */ = {isa = PBXBuildFile; fileRef = F50664F6157F52DC00AC226F /* FormController.h */; };
                F48D2AA32159740D00C6752B /* ColorCocoa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ColorCocoa.h; sourceTree = "<group>"; };
                F48D2AA42159740D00C6752B /* ColorCocoa.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ColorCocoa.mm; sourceTree = "<group>"; };
                F49786871FF45FA500E060AB /* PasteboardItemInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PasteboardItemInfo.h; sourceTree = "<group>"; };
+               F4D43D64218802E600ECECAC /* SerializedAttachmentData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SerializedAttachmentData.h; sourceTree = "<group>"; };
                F4E57EDA213F3F5F004EA98E /* FontAttributeChanges.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FontAttributeChanges.h; sourceTree = "<group>"; };
                F4E57EDF213F434A004EA98E /* WebCoreNSFontManagerExtras.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebCoreNSFontManagerExtras.h; sourceTree = "<group>"; };
                F4E57EE0213F434A004EA98E /* WebCoreNSFontManagerExtras.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = WebCoreNSFontManagerExtras.mm; sourceTree = "<group>"; };
                                93309DBB099E64910056E581 /* ReplaceSelectionCommand.h */,
                                51405C86190B014400754F94 /* SelectionRectGatherer.cpp */,
                                51405C87190B014400754F94 /* SelectionRectGatherer.h */,
+                               F4D43D64218802E600ECECAC /* SerializedAttachmentData.h */,
                                93309DC0099E64910056E581 /* SetNodeAttributeCommand.cpp */,
                                93309DC1099E64910056E581 /* SetNodeAttributeCommand.h */,
                                B8DBDB47130B0F8A00F5CDB1 /* SetSelectionCommand.cpp */,
                                415071581685067300C3C7B3 /* SelectorFilter.h in Headers */,
                                43107BE218CC19DE00CC18E8 /* SelectorPseudoTypeMap.h in Headers */,
                                E45322AC140CE267005A0F92 /* SelectorQuery.h in Headers */,
+                               F4D43D662188038B00ECECAC /* SerializedAttachmentData.h in Headers */,
                                E18DF33518AAF12C00773E59 /* SerializedCryptoKeyWrap.h in Headers */,
                                077AF14018F4AE400001ED61 /* SerializedPlatformRepresentation.h in Headers */,
                                077AF14318F4B1BB0001ED61 /* SerializedPlatformRepresentationMac.h in Headers */,
index 3595a04..0c63811 100644 (file)
 #include "ReplaceRangeWithTextCommand.h"
 #include "ReplaceSelectionCommand.h"
 #include "RuntimeEnabledFeatures.h"
+#include "SerializedAttachmentData.h"
 #include "Settings.h"
 #include "ShadowRoot.h"
+#include "SharedBuffer.h"
 #include "SimplifyMarkupCommand.h"
 #include "SpellChecker.h"
 #include "SpellingCorrectionCommand.h"
@@ -4010,6 +4012,12 @@ void Editor::registerAttachmentIdentifier(const String& identifier, const String
         client->registerAttachmentIdentifier(identifier, contentType, filePath);
 }
 
+void Editor::registerAttachments(Vector<SerializedAttachmentData>&& data)
+{
+    if (auto* client = this->client())
+        client->registerAttachments(WTFMove(data));
+}
+
 void Editor::registerAttachmentIdentifier(const String& identifier)
 {
     if (auto* client = this->client())
index b9611e3..3bc8a06 100644 (file)
@@ -86,6 +86,10 @@ struct PasteboardPlainText;
 struct PasteboardURL;
 struct TextCheckingResult;
 
+#if ENABLE(ATTACHMENT_ELEMENT)
+struct SerializedAttachmentData;
+#endif
+
 enum EditorCommandSource { CommandFromMenuOrKeyBinding, CommandFromDOM, CommandFromDOMWithUserInterface };
 enum EditorParagraphSeparator { EditorParagraphSeparatorIsDiv, EditorParagraphSeparatorIsP };
 
@@ -509,8 +513,9 @@ public:
 
 #if ENABLE(ATTACHMENT_ELEMENT)
     WEBCORE_EXPORT void insertAttachment(const String& identifier, std::optional<uint64_t>&& fileSize, const String& fileName, const String& contentType);
-    void registerAttachmentIdentifier(const String&, const String& /* contentType */, const String& /* preferredFileName */, Ref<SharedBuffer>&&);
-    void registerAttachmentIdentifier(const String&, const String& /* contentType */, const String& /* filePath */);
+    void registerAttachmentIdentifier(const String&, const String& contentType, const String& preferredFileName, Ref<SharedBuffer>&& fileData);
+    void registerAttachments(Vector<SerializedAttachmentData>&&);
+    void registerAttachmentIdentifier(const String&, const String& contentType, const String& filePath);
     void registerAttachmentIdentifier(const String&);
     void cloneAttachmentData(const String& fromIdentifier, const String& toIdentifier);
     void didInsertAttachmentElement(HTMLAttachmentElement&);
diff --git a/Source/WebCore/editing/SerializedAttachmentData.h b/Source/WebCore/editing/SerializedAttachmentData.h
new file mode 100644 (file)
index 0000000..f302823
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 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
+
+#if ENABLE(ATTACHMENT_ELEMENT)
+
+#include "SharedBuffer.h"
+#include <wtf/Ref.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class SharedBuffer;
+
+struct SerializedAttachmentData {
+    String identifier;
+    String mimeType;
+    Ref<SharedBuffer> data;
+};
+
+} // namespace WebKit
+
+#endif // ENABLE(ATTACHMENT_ELEMENT)
index 0b79eae..aefeb1a 100644 (file)
@@ -51,6 +51,7 @@
 #import "Page.h"
 #import "PublicURLManager.h"
 #import "RuntimeEnabledFeatures.h"
+#import "SerializedAttachmentData.h"
 #import "Settings.h"
 #import "SocketProvider.h"
 #import "TypedElementDescendantIterator.h"
@@ -274,6 +275,20 @@ static void replaceRichContentWithAttachments(Frame& frame, DocumentFragment& fr
             urlToResourceMap.set(url.string(), subresource.copyRef());
     }
 
+    Vector<SerializedAttachmentData> serializedAttachmentData;
+    for (auto& attachment : descendantsOfType<HTMLAttachmentElement>(fragment)) {
+        auto resourceURL = HTMLAttachmentElement::archiveResourceURL(attachment.uniqueIdentifier());
+        auto resourceEntry = urlToResourceMap.find(resourceURL.string());
+        if (resourceEntry == urlToResourceMap.end())
+            continue;
+
+        auto& resource = resourceEntry->value;
+        serializedAttachmentData.append({ attachment.uniqueIdentifier(), resource->mimeType(), resource->data() });
+    }
+
+    if (!serializedAttachmentData.isEmpty())
+        frame.editor().registerAttachments(WTFMove(serializedAttachmentData));
+
     Vector<Ref<Element>> elementsToRemove;
     Vector<AttachmentInsertionInfo> attachmentInsertionInfo;
     for (auto& image : descendantsOfType<HTMLImageElement>(fragment)) {
@@ -323,7 +338,7 @@ static void replaceRichContentWithAttachments(Frame& frame, DocumentFragment& fr
             if (is<HTMLImageElement>(originalElement.get()) && contentTypeIsSuitableForInlineImageRepresentation(info.contentType)) {
                 auto& image = downcast<HTMLImageElement>(originalElement.get());
                 image.setAttributeWithoutSynchronization(HTMLNames::srcAttr, DOMURL::createObjectURL(*frame.document(), Blob::create(info.data, info.contentType)));
-                image.setAttachmentElement(WTFMove(attachment));
+                image.setAttachmentElement(attachment.copyRef());
             } else {
                 attachment->updateAttributes(info.data->size(), info.contentType, info.fileName);
                 parent->replaceChild(attachment, WTFMove(originalElement));
index 0c68609..eeb6927 100644 (file)
@@ -38,6 +38,7 @@
 #include "MIMETypeRegistry.h"
 #include "RenderAttachment.h"
 #include "SharedBuffer.h"
+#include "URLParser.h"
 #include <pal/FileSizeFormatter.h>
 #include <wtf/IsoMallocInlines.h>
 #include <wtf/UUID.h>
@@ -85,6 +86,13 @@ const String& HTMLAttachmentElement::getAttachmentIdentifier(HTMLImageElement& i
     return identifier;
 }
 
+URL HTMLAttachmentElement::archiveResourceURL(const String& identifier)
+{
+    auto resourceURL = URLParser("applewebdata://attachment/"_s).result();
+    resourceURL.setPath(identifier);
+    return resourceURL;
+}
+
 File* HTMLAttachmentElement::file() const
 {
     return m_file.get();
index 0410426..020dc40 100644 (file)
@@ -41,6 +41,7 @@ class HTMLAttachmentElement final : public HTMLElement {
 public:
     static Ref<HTMLAttachmentElement> create(const QualifiedName&, Document&);
     static const String& getAttachmentIdentifier(HTMLImageElement&);
+    static URL archiveResourceURL(const String&);
 
     WEBCORE_EXPORT URL blobURL() const;
     WEBCORE_EXPORT File* file() const;
index 0c58452..861e399 100644 (file)
 #include "CachedResource.h"
 #include "Document.h"
 #include "DocumentLoader.h"
+#include "Editor.h"
+#include "EditorClient.h"
 #include "Frame.h"
 #include "FrameLoader.h"
 #include "FrameSelection.h"
 #include "FrameTree.h"
+#include "HTMLAttachmentElement.h"
 #include "HTMLFrameElement.h"
 #include "HTMLFrameOwnerElement.h"
 #include "HTMLIFrameElement.h"
 #include "MemoryCache.h"
 #include "Page.h"
 #include "Range.h"
+#include "RuntimeEnabledFeatures.h"
+#include "SerializedAttachmentData.h"
 #include "Settings.h"
+#include "SharedBuffer.h"
 #include "markup.h"
 #include <wtf/ListHashSet.h>
 #include <wtf/RetainPtr.h>
@@ -464,6 +470,42 @@ RefPtr<LegacyWebArchive> LegacyWebArchive::create(Range* range)
     return create(markupString, *frame, nodeList, nullptr);
 }
 
+#if ENABLE(ATTACHMENT_ELEMENT)
+
+static void addSubresourcesForAttachmentElementsIfNecessary(Frame& frame, const Vector<Node*>& nodes, Vector<Ref<ArchiveResource>>& subresources)
+{
+    if (!RuntimeEnabledFeatures::sharedFeatures().attachmentElementEnabled())
+        return;
+
+    Vector<String> identifiers;
+    for (auto* node : nodes) {
+        if (!is<HTMLAttachmentElement>(node))
+            continue;
+
+        auto uniqueIdentifier = downcast<HTMLAttachmentElement>(*node).uniqueIdentifier();
+        if (uniqueIdentifier.isEmpty())
+            continue;
+
+        identifiers.append(WTFMove(uniqueIdentifier));
+    }
+
+    if (identifiers.isEmpty())
+        return;
+
+    auto* editorClient = frame.editor().client();
+    if (!editorClient)
+        return;
+
+    auto frameName = frame.tree().uniqueName();
+    for (auto& data : editorClient->serializedAttachmentDataForIdentifiers(WTFMove(identifiers))) {
+        auto resourceURL = HTMLAttachmentElement::archiveResourceURL(data.identifier);
+        if (auto resource = ArchiveResource::create(data.data.ptr(), WTFMove(resourceURL), data.mimeType, { }, frameName))
+            subresources.append(resource.releaseNonNull());
+    }
+}
+
+#endif
+
 RefPtr<LegacyWebArchive> LegacyWebArchive::create(const String& markupString, Frame& frame, const Vector<Node*>& nodes, WTF::Function<bool (Frame&)>&& frameFilter)
 {
     auto& response = frame.loader().documentLoader()->response();
@@ -528,6 +570,10 @@ RefPtr<LegacyWebArchive> LegacyWebArchive::create(const String& markupString, Fr
         }
     }
 
+#if ENABLE(ATTACHMENT_ELEMENT)
+    addSubresourcesForAttachmentElementsIfNecessary(frame, nodes, subresources);
+#endif
+
     // If we are archiving the entire page, add any link icons that we have data for.
     if (!nodes.isEmpty() && nodes[0]->isDocumentNode()) {
         auto* documentLoader = frame.loader().documentLoader();
index e8d10c2..a65d80c 100644 (file)
@@ -27,6 +27,7 @@
 #pragma once
 
 #include "EditorInsertAction.h"
+#include "SerializedAttachmentData.h"
 #include "TextAffinity.h"
 #include "TextChecking.h"
 #include "UndoStep.h"
@@ -75,11 +76,13 @@ public:
 #if ENABLE(ATTACHMENT_ELEMENT)
     virtual void registerAttachmentIdentifier(const String& /* identifier */, const String& /* contentType */, const String& /* preferredFileName */, Ref<SharedBuffer>&&) { }
     virtual void registerAttachmentIdentifier(const String& /* identifier */, const String& /* contentType */, const String& /* filePath */) { }
+    virtual void registerAttachments(Vector<SerializedAttachmentData>&&) { }
     virtual void registerAttachmentIdentifier(const String& /* identifier */) { }
     virtual void cloneAttachmentData(const String& /* fromIdentifier */, const String& /* toIdentifier */) { }
     virtual void didInsertAttachmentWithIdentifier(const String& /* identifier */, const String& /* source */, bool /* hasEnclosingImage */) { }
     virtual void didRemoveAttachmentWithIdentifier(const String&) { }
     virtual bool supportsClientSideAttachmentData() const { return false; }
+    virtual Vector<SerializedAttachmentData> serializedAttachmentDataForIdentifiers(const Vector<String>&) { return { }; }
 #endif
 
     virtual void didBeginEditing() = 0;
index 577b315..c5d68bc 100644 (file)
@@ -1,3 +1,69 @@
+2018-10-30  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [Cocoa] Attachment dropped from one web view to another is missing its file wrapper
+        https://bugs.webkit.org/show_bug.cgi?id=190530
+        <rdar://problem/45232149>
+
+        Reviewed by Tim Horton.
+
+        See WebCore ChangeLog for more details.
+
+        * Shared/WebCoreArgumentCoders.cpp:
+        (IPC::ArgumentCoder<SerializedAttachmentData>::encode):
+        (IPC::ArgumentCoder<SerializedAttachmentData>::decode):
+        * Shared/WebCoreArgumentCoders.h:
+
+        Add IPC encoding/decoding support for SerializedAttachmentData.
+
+        * UIProcess/API/APIAttachment.cpp:
+        (API::Attachment::createSerializedRepresentation const):
+        (API::Attachment::updateFromSerializedRepresentation):
+        * UIProcess/API/APIAttachment.h:
+        * UIProcess/API/Cocoa/APIAttachmentCocoa.mm:
+        (API::Attachment::createSerializedRepresentation const):
+
+        Add a method to serialize and return attachment info as a blob of data.
+
+        (API::Attachment::updateFromSerializedRepresentation):
+
+        Add a method to update the attachment, given a serialized blob of data. On Cocoa platforms, this fails
+        gracefully if the serialized data cannot be decoded.
+
+        * UIProcess/Cocoa/PageClientImplCocoa.h:
+        * UIProcess/Cocoa/PageClientImplCocoa.mm:
+        (WebKit::PageClientImplCocoa::allocFileWrapperInstance const):
+        (WebKit::PageClientImplCocoa::serializableFileWrapperClasses const):
+        (WebKit::PageClientImplCocoa::allocFileWrapperInstance): Deleted.
+
+        Add an additional hook to return the list of NSFileWrapper subclasses suitable for deserialization. This
+        array contains (at minimum) NSFileWrapper, but may additionally include a custom NSFileWrapper subclass, if
+        configured.
+
+        * UIProcess/PageClient.h:
+        (WebKit::PageClient::allocFileWrapperInstance const):
+        (WebKit::PageClient::serializableFileWrapperClasses const):
+        (WebKit::PageClient::allocFileWrapperInstance): Deleted.
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::registerAttachmentsFromSerializedData):
+
+        Registers the given serialized attachment data, creating new _WKAttachment instances if necessary. Currently,
+        this does not update the file wrappers of existing _WKAttachments; we should revisit this in the future to see
+        if we can additionally update file wrappers for existing attachments, without breaking the case where the user
+        copies and pastes or drags and drops attachments within a single web view and the client expects _WKAttachment
+        instances to be reused.
+
+        (WebKit::WebPageProxy::serializedAttachmentDataForIdentifiers):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/WebPageProxy.messages.in:
+        * WebProcess/WebCoreSupport/WebEditorClient.cpp:
+        (WebKit::WebEditorClient::registerAttachments):
+        (WebKit::WebEditorClient::serializedAttachmentDataForIdentifiers):
+
+        Implement a new IPC hook to fetch an array of serialized attachment data blobs, given a list of attachment
+        identifiers.
+
+        * WebProcess/WebCoreSupport/WebEditorClient.h:
+
 2018-10-30  Chris Dumez  <cdumez@apple.com>
 
         [PSON] View gesture snapshot gets taken down early when process-swapping
index c4c4788..ac4d9be 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "DataReference.h"
 #include "ShareableBitmap.h"
+#include "SharedBufferDataReference.h"
 #include <WebCore/AuthenticationChallenge.h>
 #include <WebCore/BlobPart.h>
 #include <WebCore/CacheQueryOptions.h>
@@ -69,6 +70,7 @@
 #include <WebCore/ScrollingCoordinator.h>
 #include <WebCore/SearchPopupMenu.h>
 #include <WebCore/SecurityOrigin.h>
+#include <WebCore/SerializedAttachmentData.h>
 #include <WebCore/ServiceWorkerClientData.h>
 #include <WebCore/ServiceWorkerClientIdentifier.h>
 #include <WebCore/ServiceWorkerData.h>
@@ -3012,4 +3014,30 @@ std::optional<FontAttributes> ArgumentCoder<FontAttributes>::decode(Decoder& dec
     return attributes;
 }
 
+#if ENABLE(ATTACHMENT_ELEMENT)
+
+void ArgumentCoder<SerializedAttachmentData>::encode(IPC::Encoder& encoder, const WebCore::SerializedAttachmentData& data)
+{
+    encoder << data.identifier << data.mimeType << IPC::SharedBufferDataReference { data.data.get() };
+}
+
+std::optional<SerializedAttachmentData> ArgumentCoder<WebCore::SerializedAttachmentData>::decode(IPC::Decoder& decoder)
+{
+    String identifier;
+    if (!decoder.decode(identifier))
+        return std::nullopt;
+
+    String mimeType;
+    if (!decoder.decode(mimeType))
+        return std::nullopt;
+
+    IPC::DataReference data;
+    if (!decoder.decode(data))
+        return std::nullopt;
+
+    return {{ WTFMove(identifier), WTFMove(mimeType), WebCore::SharedBuffer::create(data.data(), data.size()) }};
+}
+
+#endif // ENABLE(ATTACHMENT_ELEMENT)
+
 } // namespace IPC
index d87818e..f509216 100644 (file)
@@ -164,6 +164,10 @@ class MediaSessionMetadata;
 struct MediaConstraints;
 #endif
 
+#if ENABLE(ATTACHMENT_ELEMENT)
+struct SerializedAttachmentData;
+#endif
+
 #if ENABLE(INDEXED_DATABASE)
 using IDBKeyPath = Variant<String, Vector<String>>;
 #endif
@@ -720,6 +724,15 @@ template<> struct ArgumentCoder<WebCore::FontAttributes> {
     static std::optional<WebCore::FontAttributes> decode(Decoder&);
 };
 
+#if ENABLE(ATTACHMENT_ELEMENT)
+
+template<> struct ArgumentCoder<WebCore::SerializedAttachmentData> {
+    static void encode(Encoder&, const WebCore::SerializedAttachmentData&);
+    static std::optional<WebCore::SerializedAttachmentData> decode(Decoder&);
+};
+
+#endif // ENABLE(ATTACHMENT_ELEMENT)
+
 } // namespace IPC
 
 namespace WTF {
index 9cc0410..a134bfa 100644 (file)
@@ -96,6 +96,15 @@ RefPtr<WebCore::SharedBuffer> Attachment::enclosingImageData() const
     return nullptr;
 }
 
+RefPtr<WebCore::SharedBuffer> Attachment::createSerializedRepresentation() const
+{
+    return nullptr;
+}
+
+void Attachment::updateFromSerializedRepresentation(Ref<WebCore::SharedBuffer>&&, const WTF::String&)
+{
+}
+
 #endif // !PLATFORM(COCOA)
 
 }
index b8fbbbe..64d0241 100644 (file)
@@ -85,6 +85,9 @@ public:
     void setHasEnclosingImage(bool hasEnclosingImage) { m_hasEnclosingImage = hasEnclosingImage; }
     bool hasEnclosingImage() const { return m_hasEnclosingImage; }
 
+    RefPtr<WebCore::SharedBuffer> createSerializedRepresentation() const;
+    void updateFromSerializedRepresentation(Ref<WebCore::SharedBuffer>&&, const WTF::String& contentType);
+
 private:
     explicit Attachment(const WTF::String& identifier, WebKit::WebPageProxy&);
 
index b877102..b4ec9c8 100644 (file)
@@ -26,6 +26,7 @@
 #import "config.h"
 #import "APIAttachment.h"
 
+#import "PageClient.h"
 #import <WebCore/MIMETypeRegistry.h>
 #import <WebCore/SharedBuffer.h>
 #if PLATFORM(IOS_FAMILY)
@@ -33,6 +34,7 @@
 #else
 #import <CoreServices/CoreServices.h>
 #endif
+#import <pal/spi/cocoa/NSKeyedArchiverSPI.h>
 
 namespace API {
 
@@ -125,4 +127,33 @@ bool Attachment::isEmpty() const
     return !m_fileWrapper;
 }
 
+RefPtr<WebCore::SharedBuffer> Attachment::createSerializedRepresentation() const
+{
+    if (!m_fileWrapper || !m_webPage)
+        return nullptr;
+
+    NSData *serializedData = securelyArchivedDataWithRootObject(m_fileWrapper.get());
+    if (!serializedData)
+        return nullptr;
+
+    return WebCore::SharedBuffer::create(serializedData);
+}
+
+void Attachment::updateFromSerializedRepresentation(Ref<WebCore::SharedBuffer>&& serializedRepresentation, const WTF::String& contentType)
+{
+    if (!m_webPage)
+        return;
+
+    auto serializedData = serializedRepresentation->createNSData();
+    if (!serializedData)
+        return;
+
+    NSFileWrapper *fileWrapper = unarchivedObjectOfClassesFromData(m_webPage->pageClient().serializableFileWrapperClasses(), serializedData.get());
+    if (!fileWrapper)
+        return;
+
+    setFileWrapperAndUpdateContentType(fileWrapper, contentType);
+    m_webPage->updateAttachmentAttributes(*this, [] (auto) { });
+}
+
 } // namespace API
index d5154ed..d727372 100644 (file)
@@ -45,7 +45,8 @@ public:
 #if ENABLE(ATTACHMENT_ELEMENT)
     void didInsertAttachment(API::Attachment&, const String& source) final;
     void didRemoveAttachment(API::Attachment&) final;
-    NSFileWrapper *allocFileWrapperInstance() final;
+    NSFileWrapper *allocFileWrapperInstance() const final;
+    NSSet *serializableFileWrapperClasses() const final;
 #endif
 
 protected:
index 1f9d292..75ace9a 100644 (file)
@@ -66,7 +66,7 @@ void PageClientImplCocoa::didRemoveAttachment(API::Attachment& attachment)
 #endif
 }
 
-NSFileWrapper *PageClientImplCocoa::allocFileWrapperInstance()
+NSFileWrapper *PageClientImplCocoa::allocFileWrapperInstance() const
 {
 #if WK_API_ENABLED
     Class cls = m_webView.configuration._attachmentFileWrapperClass ?: [NSFileWrapper self];
@@ -76,6 +76,17 @@ NSFileWrapper *PageClientImplCocoa::allocFileWrapperInstance()
 #endif
 }
 
+NSSet *PageClientImplCocoa::serializableFileWrapperClasses() const
+{
+    Class defaultFileWrapperClass = NSFileWrapper.self;
+#if WK_API_ENABLED
+    Class configuredFileWrapperClass = m_webView.configuration._attachmentFileWrapperClass;
+    if (configuredFileWrapperClass && configuredFileWrapperClass != defaultFileWrapperClass)
+        return [NSSet setWithObjects:configuredFileWrapperClass, defaultFileWrapperClass, nil];
+#endif
+    return [NSSet setWithObjects:defaultFileWrapperClass, nil];
+}
+
 #endif
     
 }
index b8a0c14..1deed29 100644 (file)
@@ -45,6 +45,7 @@
 
 OBJC_CLASS CALayer;
 OBJC_CLASS NSFileWrapper;
+OBJC_CLASS NSSet;
 OBJC_CLASS _WKRemoteObjectRegistry;
 
 #if USE(APPKIT)
@@ -445,7 +446,8 @@ public:
     virtual void didInsertAttachment(API::Attachment&, const String& source) { }
     virtual void didRemoveAttachment(API::Attachment&) { }
 #if PLATFORM(COCOA)
-    virtual NSFileWrapper *allocFileWrapperInstance() { return nullptr; }
+    virtual NSFileWrapper *allocFileWrapperInstance() const { return nullptr; }
+    virtual NSSet *serializableFileWrapperClasses() const { return nullptr; }
 #endif
 #endif
 };
index 8ec4bfd..e63ecf5 100644 (file)
@@ -7900,6 +7900,15 @@ void WebPageProxy::registerAttachmentIdentifier(const String& identifier)
         m_attachmentIdentifierToAttachmentMap.set(identifier, ensureAttachment(identifier));
 }
 
+void WebPageProxy::registerAttachmentsFromSerializedData(Vector<WebCore::SerializedAttachmentData>&& data)
+{
+    for (auto& serializedData : data) {
+        auto identifier = WTFMove(serializedData.identifier);
+        if (!attachmentForIdentifier(identifier))
+            ensureAttachment(identifier)->updateFromSerializedRepresentation(WTFMove(serializedData.data), WTFMove(serializedData.mimeType));
+    }
+}
+
 void WebPageProxy::cloneAttachmentData(const String& fromIdentifier, const String& toIdentifier)
 {
     auto newAttachment = ensureAttachment(toIdentifier);
@@ -7925,6 +7934,21 @@ void WebPageProxy::invalidateAllAttachments()
     m_attachmentIdentifierToAttachmentMap.clear();
 }
 
+void WebPageProxy::serializedAttachmentDataForIdentifiers(const Vector<String>& identifiers, Vector<WebCore::SerializedAttachmentData>& serializedData)
+{
+    for (auto identifier : identifiers) {
+        auto attachment = attachmentForIdentifier(identifier);
+        if (!attachment)
+            continue;
+
+        auto data = attachment->createSerializedRepresentation();
+        if (!data)
+            continue;
+
+        serializedData.append({ identifier, attachment->mimeType(), data.releaseNonNull() });
+    }
+}
+
 #if !PLATFORM(COCOA)
 
 void WebPageProxy::platformRegisterAttachment(Ref<API::Attachment>&&, const String&, const IPC::DataReference&)
index 0c7ae5d..258af92 100644 (file)
@@ -231,6 +231,7 @@ class RemoteLayerTreeScrollingPerformanceData;
 class RemoteLayerTreeTransaction;
 class RemoteScrollingCoordinatorProxy;
 class SecKeyProxyStore;
+class SharedBufferDataReference;
 class UserData;
 class ViewSnapshot;
 class VisitedLinkStore;
@@ -1344,6 +1345,7 @@ public:
     RefPtr<API::Attachment> attachmentForIdentifier(const String& identifier) const;
     void insertAttachment(Ref<API::Attachment>&&, Function<void(CallbackBase::Error)>&&);
     void updateAttachmentAttributes(const API::Attachment&, Function<void(CallbackBase::Error)>&&);
+    void serializedAttachmentDataForIdentifiers(const Vector<String>&, Vector<WebCore::SerializedAttachmentData>&);
 #endif
 
 #if ENABLE(APPLICATION_MANIFEST)
@@ -1829,6 +1831,7 @@ private:
 #if ENABLE(ATTACHMENT_ELEMENT)
     void registerAttachmentIdentifierFromData(const String&, const String& contentType, const String& preferredFileName, const IPC::DataReference&);
     void registerAttachmentIdentifierFromFilePath(const String&, const String& contentType, const String& filePath);
+    void registerAttachmentsFromSerializedData(Vector<WebCore::SerializedAttachmentData>&&);
     void registerAttachmentIdentifier(const String&);
     void cloneAttachmentData(const String& fromIdentifier, const String& toIdentifier);
 
index ccb9864..1fe860e 100644 (file)
@@ -534,9 +534,11 @@ messages -> WebPageProxy {
     RegisterAttachmentIdentifierFromData(String identifier, String contentType, String preferredFileName, IPC::SharedBufferDataReference data)
     RegisterAttachmentIdentifierFromFilePath(String identifier, String contentType, String filePath)
     RegisterAttachmentIdentifier(String identifier)
+    registerAttachmentsFromSerializedData(Vector<WebCore::SerializedAttachmentData> data)
     CloneAttachmentData(String fromIdentifier, String toIdentifier)
     DidInsertAttachmentWithIdentifier(String identifier, String source, bool hasEnclosingImage)
     DidRemoveAttachmentWithIdentifier(String identifier)
+    SerializedAttachmentDataForIdentifiers(Vector<String> identifiers) -> (Vector<WebCore::SerializedAttachmentData> seralizedData) LegacySync
 #endif
 
 #if PLATFORM(MAC) && ENABLE(WEBPROCESS_WINDOWSERVER_BLOCKING)
index 7b90f59..acde2f0 100644 (file)
@@ -49,6 +49,7 @@
 #include <WebCore/KeyboardEvent.h>
 #include <WebCore/NotImplemented.h>
 #include <WebCore/Page.h>
+#include <WebCore/SerializedAttachmentData.h>
 #include <WebCore/SpellChecker.h>
 #include <WebCore/StyleProperties.h>
 #include <WebCore/TextIterator.h>
@@ -165,6 +166,11 @@ void WebEditorClient::registerAttachmentIdentifier(const String& identifier, con
     m_page->send(Messages::WebPageProxy::RegisterAttachmentIdentifierFromData(identifier, contentType, preferredFileName, { data }));
 }
 
+void WebEditorClient::registerAttachments(Vector<WebCore::SerializedAttachmentData>&& data)
+{
+    m_page->send(Messages::WebPageProxy::registerAttachmentsFromSerializedData(WTFMove(data)));
+}
+
 void WebEditorClient::registerAttachmentIdentifier(const String& identifier, const String& contentType, const String& filePath)
 {
     m_page->send(Messages::WebPageProxy::RegisterAttachmentIdentifierFromFilePath(identifier, contentType, filePath));
@@ -190,6 +196,13 @@ void WebEditorClient::didRemoveAttachmentWithIdentifier(const String& identifier
     m_page->send(Messages::WebPageProxy::DidRemoveAttachmentWithIdentifier(identifier));
 }
 
+Vector<SerializedAttachmentData> WebEditorClient::serializedAttachmentDataForIdentifiers(const Vector<String>& identifiers)
+{
+    Vector<WebCore::SerializedAttachmentData> serializedData;
+    m_page->sendSync(Messages::WebPageProxy::SerializedAttachmentDataForIdentifiers(identifiers), Messages::WebPageProxy::SerializedAttachmentDataForIdentifiers::Reply(serializedData));
+    return serializedData;
+}
+
 #endif
 
 void WebEditorClient::didApplyStyle()
index dd6fe69..4b5a6d3 100644 (file)
@@ -63,10 +63,12 @@ private:
     void registerAttachmentIdentifier(const String&, const String& contentType, const String& preferredFileName, Ref<WebCore::SharedBuffer>&&) final;
     void registerAttachmentIdentifier(const String&, const String& contentType, const String& filePath) final;
     void registerAttachmentIdentifier(const String&) final;
+    void registerAttachments(Vector<WebCore::SerializedAttachmentData>&&) final;
     void cloneAttachmentData(const String& fromIdentifier, const String& toIdentifier) final;
     void didInsertAttachmentWithIdentifier(const String& identifier, const String& source, bool hasEnclosingImage) final;
     void didRemoveAttachmentWithIdentifier(const String& identifier) final;
     bool supportsClientSideAttachmentData() const final { return true; }
+    Vector<WebCore::SerializedAttachmentData> serializedAttachmentDataForIdentifiers(const Vector<String>&) final;
 #endif
 
     void didBeginEditing() final;
index c242cf3..84c1e98 100644 (file)
@@ -1,3 +1,21 @@
+2018-10-30  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [Cocoa] Attachment dropped from one web view to another is missing its file wrapper
+        https://bugs.webkit.org/show_bug.cgi?id=190530
+        <rdar://problem/45232149>
+
+        Reviewed by Tim Horton.
+
+        Add a test to verify that copying different types of attachments and pasting in a new web view inserts
+        attachments in the second web view that are backed by _WKAttachment objects, whose NSFileWrappers hold data that
+        is equivalent to the original file wrappers used to insert attachments in the first web view.
+
+        Existing API tests verify that when copying and pasting within a single web view, the pasted attachment element
+        is still backed by the same NSFileWrapper instance.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm:
+        (TestWebKitAPI::TEST):
+
 2018-10-30  Fujii Hironori  <Hironori.Fujii@sony.com>
 
         [Win] Remove obsolete code for Visual Studio Express in webkitdirs.pm
index 93fb87a..7964df1 100644 (file)
@@ -1462,6 +1462,52 @@ TEST(WKAttachmentTests, CustomFileWrapperSubclass)
     EXPECT_EQ([FileWrapper self], [insertedAttachments.firstObject.info.fileWrapper class]);
 }
 
+TEST(WKAttachmentTests, CopyAndPasteBetweenWebViews)
+{
+    auto file = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testHTMLData()]);
+    [file setPreferredFilename:@"test.foobar"];
+    auto image = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testImageData()]);
+    auto document = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testPDFData()]);
+    auto folder = adoptNS([[NSFileWrapper alloc] initDirectoryWithFileWrappers:@{ @"image.png": image.get(), @"document.pdf": document.get() }]);
+    [folder setPreferredFilename:@"folder"];
+    auto archive = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testZIPData()]);
+    [archive setPreferredFilename:@"archive"];
+
+    @autoreleasepool {
+        auto firstWebView = webViewForTestingAttachments();
+        [firstWebView synchronouslyInsertAttachmentWithFileWrapper:file.get() contentType:@"application/octet-stream"];
+        [firstWebView synchronouslyInsertAttachmentWithFileWrapper:folder.get() contentType:(__bridge NSString *)kUTTypeFolder];
+        [firstWebView synchronouslyInsertAttachmentWithFileWrapper:archive.get() contentType:@"application/zip"];
+        [firstWebView selectAll:nil];
+        [firstWebView _executeEditCommand:@"Copy" argument:nil completion:nil];
+    }
+
+    auto secondWebView = webViewForTestingAttachments();
+    ObserveAttachmentUpdatesForScope observer(secondWebView.get());
+    [secondWebView paste:nil];
+    [secondWebView expectElementCount:3 tagName:@"attachment"];
+    EXPECT_EQ(3U, observer.observer().inserted.count);
+
+    NSString *plainFileIdentifier = [secondWebView stringByEvaluatingJavaScript:@"document.querySelector('attachment[title^=test]').uniqueIdentifier"];
+    NSString *folderIdentifier = [secondWebView stringByEvaluatingJavaScript:@"document.querySelector('attachment[title=folder]').uniqueIdentifier"];
+    NSString *archiveIdentifier = [secondWebView stringByEvaluatingJavaScript:@"document.querySelector('attachment[title=archive]').uniqueIdentifier"];
+
+    _WKAttachmentInfo *pastedFileInfo = [secondWebView _attachmentForIdentifier:plainFileIdentifier].info;
+    _WKAttachmentInfo *pastedFolderInfo = [secondWebView _attachmentForIdentifier:folderIdentifier].info;
+    _WKAttachmentInfo *pastedArchiveInfo = [secondWebView _attachmentForIdentifier:archiveIdentifier].info;
+
+    NSDictionary<NSString *, NSFileWrapper *> *pastedFolderContents = pastedFolderInfo.fileWrapper.fileWrappers;
+    NSFileWrapper *documentFromFolder = [pastedFolderContents objectForKey:@"document.pdf"];
+    NSFileWrapper *imageFromFolder = [pastedFolderContents objectForKey:@"image.png"];
+    EXPECT_TRUE([[document regularFileContents] isEqualToData:documentFromFolder.regularFileContents]);
+    EXPECT_TRUE([[image regularFileContents] isEqualToData:imageFromFolder.regularFileContents]);
+    EXPECT_TRUE([[file regularFileContents] isEqualToData:pastedFileInfo.fileWrapper.regularFileContents]);
+    EXPECT_TRUE([[archive regularFileContents] isEqualToData:pastedArchiveInfo.fileWrapper.regularFileContents]);
+    EXPECT_WK_STREQ("application/octet-stream", pastedFileInfo.contentType);
+    EXPECT_WK_STREQ("public.directory", pastedFolderInfo.contentType);
+    EXPECT_WK_STREQ("application/zip", pastedArchiveInfo.contentType);
+}
+
 #pragma mark - Platform-specific tests
 
 #if PLATFORM(MAC)