No DOM API to instantiate an attachment for an img element
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 29 Sep 2018 02:12:57 +0000 (02:12 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 29 Sep 2018 02:12:57 +0000 (02:12 +0000)
https://bugs.webkit.org/show_bug.cgi?id=189934
<rdar://problem/44743222>

Reviewed by Ryosuke Niwa.

Source/WebCore:

Adds support for HTMLAttachmentElement.getAttachmentIdentifier, a function that internal WebKit clients can use
to ensure that an image element is backed by a unique _WKAttachment. See below for more details.

Tests:  WKAttachmentTests.AddAttachmentToConnectedImageElement
        WKAttachmentTests.ChangeFileWrapperForPastedImage
        WKAttachmentTests.ConnectImageWithAttachmentToDocument

* dom/Document.cpp:
(WebCore::Document::registerAttachmentIdentifier):

Add a new hook to register an empty _WKAttachment in the UI process with a given identifier. Used when creating
a new empty attachment to back an image element.

* dom/Document.h:
* editing/Editor.cpp:
(WebCore::Editor::registerAttachmentIdentifier):
(WebCore::Editor::notifyClientOfAttachmentUpdates):
* editing/Editor.h:
* html/HTMLAttachmentElement.cpp:
(WebCore::HTMLAttachmentElement::getAttachmentIdentifier):

Creates an attachment element to back the image element, if an attachment does not already exist, and returns
the unique identifier. This also causes an empty corresponding _WKAttachment to be created in the client, whose
file wrapper determines the contents of the image.

(WebCore::HTMLAttachmentElement::ensureUniqueIdentifier):
(WebCore::HTMLAttachmentElement::hasEnclosingImage const):
(WebCore::HTMLAttachmentElement::updateEnclosingImageWithData):

Add a helper that updates the source of the enclosing image element given a content type and image data, by
creating a new blob and blob URL.

* html/HTMLAttachmentElement.h:
* html/HTMLAttachmentElement.idl:
* html/HTMLImageElement.idl:

Rename webkitAttachmentIdentifier to just attachmentIdentifier.

* page/EditorClient.h:
(WebCore::EditorClient::registerAttachmentIdentifier):
(WebCore::EditorClient::didInsertAttachmentWithIdentifier):

Source/WebKit:

Makes some adjustments to support using _WKAttachment's file wrapper to change the contents of any image element
hosting the attachment in its shadow root. To do this, we add some plumbing to allow the UI process to update an
attachment element's enclosing image with data from its file wrapper.

* UIProcess/API/APIAttachment.cpp:
(API::Attachment::isEmpty const):
(API::Attachment::enclosingImageData const):

Helper method that creates a SharedBuffer representing image data for the attachment. Only returns a non-null
value for attachment elements that are enclosed within an image.

* UIProcess/API/APIAttachment.h:
* UIProcess/API/Cocoa/APIAttachmentCocoa.mm:
(API::Attachment::enclosingImageData const):
(API::Attachment::isEmpty const):
* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _attachmentForIdentifier:]):

Add new SPI to request a _WKAttachment for a given unique identifier. Currently, this is only used for testing.

* UIProcess/API/Cocoa/WKWebViewPrivate.h:
* UIProcess/Cocoa/WebPageProxyCocoa.mm:
(WebKit::WebPageProxy::platformRegisterAttachment):
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::updateAttachmentAttributes):
(WebKit::WebPageProxy::registerAttachmentIdentifier):
(WebKit::WebPageProxy::didInsertAttachmentWithIdentifier):

Plumb whether or not the attachment is enclosed by an image from the web process to the UI process.

(WebKit::WebPageProxy::didRemoveAttachmentWithIdentifier):
(WebKit::WebPageProxy::didInsertAttachment): Deleted.
(WebKit::WebPageProxy::didRemoveAttachment):
* UIProcess/WebPageProxy.h:
* UIProcess/WebPageProxy.messages.in:
* WebProcess/WebCoreSupport/WebEditorClient.cpp:
(WebKit::WebEditorClient::registerAttachmentIdentifier):
(WebKit::WebEditorClient::didInsertAttachmentWithIdentifier):

Update attachment attributes after inserting an attachment. This ensures that an attachment that was created and
later inserted via script into the document will be synced with state in the UI process, if the client has
changed the contents of the attachment.

* WebProcess/WebCoreSupport/WebEditorClient.h:
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::updateAttachmentAttributes):

Plumb attachment data from the UI process to the web process.

* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:

Tools:

Adds 3 new API tests in WKAttachmentTests:

`AddAttachmentToConnectedImageElement` verifies that an image element that's already in the document can gain an
attachment element via `HTMLAttachmentElement.getAttachmentIdentifier`.

`ChangeFileWrapperForPastedImage` verifies that an image that has been pasted produces a _WKAttachment in the UI
process, and changing the file wrapper of that _WKAttachment changes the pasted image.

`ConnectImageWithAttachmentToDocument` verifies that script can create an image element, ensure that it has an
attachment, and set a file wrapper for the generated _WKAttachment. Connecting the image to the document should
then result in an image element with the contents of the _WKAttachment's file wrapper.

* TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm:
(-[TestWKWebView imageElementSize]):
(-[TestWKWebView waitForImageElementSizeToBecome:]):
(TestWebKitAPI::TEST):
(-[TestWKWebView waitForAttachmentElementSizeToBecome:]): Deleted.

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

27 files changed:
Source/WebCore/ChangeLog
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/editing/Editor.cpp
Source/WebCore/editing/Editor.h
Source/WebCore/html/HTMLAttachmentElement.cpp
Source/WebCore/html/HTMLAttachmentElement.h
Source/WebCore/html/HTMLAttachmentElement.idl
Source/WebCore/html/HTMLImageElement.idl
Source/WebCore/page/EditorClient.h
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/API/APIAttachment.cpp
Source/WebKit/UIProcess/API/APIAttachment.h
Source/WebKit/UIProcess/API/Cocoa/APIAttachmentCocoa.mm
Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h
Source/WebKit/UIProcess/Cocoa/WebPageProxyCocoa.mm
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
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/WebPage.messages.in
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm

index 3de6497..3c7f5c0 100644 (file)
@@ -1,3 +1,53 @@
+2018-09-28  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        No DOM API to instantiate an attachment for an img element
+        https://bugs.webkit.org/show_bug.cgi?id=189934
+        <rdar://problem/44743222>
+
+        Reviewed by Ryosuke Niwa.
+
+        Adds support for HTMLAttachmentElement.getAttachmentIdentifier, a function that internal WebKit clients can use
+        to ensure that an image element is backed by a unique _WKAttachment. See below for more details.
+
+        Tests:  WKAttachmentTests.AddAttachmentToConnectedImageElement
+                WKAttachmentTests.ChangeFileWrapperForPastedImage
+                WKAttachmentTests.ConnectImageWithAttachmentToDocument
+
+        * dom/Document.cpp:
+        (WebCore::Document::registerAttachmentIdentifier):
+
+        Add a new hook to register an empty _WKAttachment in the UI process with a given identifier. Used when creating
+        a new empty attachment to back an image element.
+
+        * dom/Document.h:
+        * editing/Editor.cpp:
+        (WebCore::Editor::registerAttachmentIdentifier):
+        (WebCore::Editor::notifyClientOfAttachmentUpdates):
+        * editing/Editor.h:
+        * html/HTMLAttachmentElement.cpp:
+        (WebCore::HTMLAttachmentElement::getAttachmentIdentifier):
+
+        Creates an attachment element to back the image element, if an attachment does not already exist, and returns
+        the unique identifier. This also causes an empty corresponding _WKAttachment to be created in the client, whose
+        file wrapper determines the contents of the image.
+
+        (WebCore::HTMLAttachmentElement::ensureUniqueIdentifier):
+        (WebCore::HTMLAttachmentElement::hasEnclosingImage const):
+        (WebCore::HTMLAttachmentElement::updateEnclosingImageWithData):
+
+        Add a helper that updates the source of the enclosing image element given a content type and image data, by
+        creating a new blob and blob URL.
+
+        * html/HTMLAttachmentElement.h:
+        * html/HTMLAttachmentElement.idl:
+        * html/HTMLImageElement.idl:
+
+        Rename webkitAttachmentIdentifier to just attachmentIdentifier.
+
+        * page/EditorClient.h:
+        (WebCore::EditorClient::registerAttachmentIdentifier):
+        (WebCore::EditorClient::didInsertAttachmentWithIdentifier):
+
 2018-09-28  Chris Dumez  <cdumez@apple.com>
 
         The return value of an OnBeforeUnloadEventHandler should always be coerced into a DOMString
index e3e6224..19590b0 100644 (file)
@@ -8109,6 +8109,12 @@ Vector<RefPtr<WebAnimation>> Document::getAnimations()
 
 #if ENABLE(ATTACHMENT_ELEMENT)
 
+void Document::registerAttachmentIdentifier(const String& identifier)
+{
+    if (auto* frame = this->frame())
+        frame->editor().registerAttachmentIdentifier(identifier);
+}
+
 void Document::didInsertAttachmentElement(HTMLAttachmentElement& attachment)
 {
     auto identifier = attachment.uniqueIdentifier();
index 9508e13..13543b3 100644 (file)
@@ -1462,9 +1462,10 @@ public:
     Vector<RefPtr<WebAnimation>> getAnimations();
         
 #if ENABLE(ATTACHMENT_ELEMENT)
+    void registerAttachmentIdentifier(const String&);
     void didInsertAttachmentElement(HTMLAttachmentElement&);
     void didRemoveAttachmentElement(HTMLAttachmentElement&);
-    WEBCORE_EXPORT RefPtr<HTMLAttachmentElement> attachmentForIdentifier(const String& identifier) const;
+    WEBCORE_EXPORT RefPtr<HTMLAttachmentElement> attachmentForIdentifier(const String&) const;
     const HashMap<String, Ref<HTMLAttachmentElement>>& attachmentElementsByIdentifier() const { return m_attachmentIdentifierToElementMap; }
 #endif
 
index e3b4436..67e7eee 100644 (file)
@@ -3943,6 +3943,12 @@ void Editor::registerAttachmentIdentifier(const String& identifier, const String
         client->registerAttachmentIdentifier(identifier, contentType, filePath);
 }
 
+void Editor::registerAttachmentIdentifier(const String& identifier)
+{
+    if (auto* client = this->client())
+        client->registerAttachmentIdentifier(identifier);
+}
+
 void Editor::cloneAttachmentData(const String& fromIdentifier, const String& toIdentifier)
 {
     if (auto* client = this->client())
@@ -3987,7 +3993,7 @@ void Editor::notifyClientOfAttachmentUpdates()
 
     for (auto& identifier : insertedAttachmentIdentifiers) {
         if (auto attachment = document->attachmentForIdentifier(identifier))
-            client()->didInsertAttachmentWithIdentifier(identifier, attachment->attributeWithoutSynchronization(HTMLNames::srcAttr));
+            client()->didInsertAttachmentWithIdentifier(identifier, attachment->attributeWithoutSynchronization(HTMLNames::srcAttr), attachment->hasEnclosingImage());
         else
             ASSERT_NOT_REACHED();
     }
index db94aff..6ff2866 100644 (file)
@@ -511,6 +511,7 @@ public:
     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&);
     void cloneAttachmentData(const String& fromIdentifier, const String& toIdentifier);
     void didInsertAttachmentElement(HTMLAttachmentElement&);
     void didRemoveAttachmentElement(HTMLAttachmentElement&);
index 6d1b2d1..3049cd9 100644 (file)
 #include "Editor.h"
 #include "File.h"
 #include "Frame.h"
+#include "HTMLImageElement.h"
 #include "HTMLNames.h"
+#include "MIMETypeRegistry.h"
 #include "RenderAttachment.h"
 #include "SharedBuffer.h"
 #include <pal/FileSizeFormatter.h>
 #include <wtf/IsoMallocInlines.h>
 #include <wtf/UUID.h>
 
+#if PLATFORM(COCOA)
+#include "UTIUtilities.h"
+#endif
+
 namespace WebCore {
 
 WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLAttachmentElement);
@@ -64,6 +70,21 @@ RenderPtr<RenderElement> HTMLAttachmentElement::createElementRenderer(RenderStyl
     return createRenderer<RenderAttachment>(*this, WTFMove(style));
 }
 
+const String& HTMLAttachmentElement::getAttachmentIdentifier(HTMLImageElement& image)
+{
+    if (auto attachment = image.attachmentElement())
+        return attachment->uniqueIdentifier();
+
+    auto& document = image.document();
+    auto attachment = create(HTMLNames::attachmentTag, document);
+    auto& identifier = attachment->ensureUniqueIdentifier();
+
+    document.registerAttachmentIdentifier(identifier);
+    image.setAttachmentElement(WTFMove(attachment));
+
+    return identifier;
+}
+
 File* HTMLAttachmentElement::file() const
 {
     return m_file.get();
@@ -109,13 +130,18 @@ void HTMLAttachmentElement::removedFromAncestor(RemovalType type, ContainerNode&
         document().didRemoveAttachmentElement(*this);
 }
 
-String HTMLAttachmentElement::ensureUniqueIdentifier()
+const String& HTMLAttachmentElement::ensureUniqueIdentifier()
 {
     if (m_uniqueIdentifier.isEmpty())
         m_uniqueIdentifier = createCanonicalUUIDString();
     return m_uniqueIdentifier;
 }
 
+bool HTMLAttachmentElement::hasEnclosingImage() const
+{
+    return is<HTMLImageElement>(shadowHost());
+}
+
 void HTMLAttachmentElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
 {
     if (name == progressAttr || name == subtitleAttr || name == titleAttr || name == typeAttr) {
@@ -165,6 +191,24 @@ void HTMLAttachmentElement::updateAttributes(std::optional<uint64_t>&& newFileSi
         renderer->invalidate();
 }
 
+void HTMLAttachmentElement::updateEnclosingImageWithData(const String& contentType, Ref<SharedBuffer>&& data)
+{
+    auto* hostElement = shadowHost();
+    if (!is<HTMLImageElement>(hostElement) || !data->size())
+        return;
+
+    String mimeType = contentType;
+#if PLATFORM(COCOA)
+    if (isDeclaredUTI(contentType))
+        mimeType = MIMETypeFromUTI(contentType);
+#endif
+
+    if (!MIMETypeRegistry::isSupportedImageMIMEType(mimeType))
+        return;
+
+    hostElement->setAttributeWithoutSynchronization(HTMLNames::srcAttr, DOMURL::createObjectURL(document(), Blob::create(WTFMove(data), mimeType)));
+}
+
 } // namespace WebCore
 
 #endif // ENABLE(ATTACHMENT_ELEMENT)
index 2f24221..1ec60fd 100644 (file)
 namespace WebCore {
 
 class File;
+class HTMLImageElement;
 class RenderAttachment;
+class SharedBuffer;
 
 class HTMLAttachmentElement final : public HTMLElement {
     WTF_MAKE_ISO_ALLOCATED(HTMLAttachmentElement);
 public:
     static Ref<HTMLAttachmentElement> create(const QualifiedName&, Document&);
+    static const String& getAttachmentIdentifier(HTMLImageElement&);
 
     WEBCORE_EXPORT URL blobURL() const;
     WEBCORE_EXPORT File* file() const;
@@ -49,11 +52,13 @@ public:
     void setUniqueIdentifier(const String& uniqueIdentifier) { m_uniqueIdentifier = uniqueIdentifier; }
 
     WEBCORE_EXPORT void updateAttributes(std::optional<uint64_t>&& newFileSize, const String& newContentType, const String& newFilename);
+    WEBCORE_EXPORT void updateEnclosingImageWithData(const String& contentType, Ref<SharedBuffer>&& data);
 
     InsertedIntoAncestorResult insertedIntoAncestor(InsertionType, ContainerNode&) final;
     void removedFromAncestor(RemovalType, ContainerNode&) final;
 
-    String ensureUniqueIdentifier();
+    const String& ensureUniqueIdentifier();
+    bool hasEnclosingImage() const;
 
     WEBCORE_EXPORT String attachmentTitle() const;
     String attachmentType() const;
index d2f709f..522e257 100644 (file)
@@ -29,4 +29,6 @@
 ] interface HTMLAttachmentElement : HTMLElement {
     attribute File? file;
     readonly attribute DOMString uniqueIdentifier;
+
+    static DOMString getAttachmentIdentifier(HTMLImageElement imageElement);
 };
index 3790ac7..a7e5dbd 100644 (file)
@@ -42,7 +42,7 @@
     attribute unsigned long width;
     [Reflect] attribute DOMString decoding;
 
-    [Conditional=ATTACHMENT_ELEMENT, EnabledAtRuntime=AttachmentElement, ImplementedAs=attachmentIdentifier] readonly attribute DOMString webkitAttachmentIdentifier;
+    [Conditional=ATTACHMENT_ELEMENT, EnabledAtRuntime=AttachmentElement] readonly attribute DOMString attachmentIdentifier;
 
     // Extensions
     readonly attribute boolean complete;
index e9b1585..c5cbc71 100644 (file)
@@ -75,8 +75,9 @@ 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 registerAttachmentIdentifier(const String& /* identifier */) { }
     virtual void cloneAttachmentData(const String& /* fromIdentifier */, const String& /* toIdentifier */) { }
-    virtual void didInsertAttachmentWithIdentifier(const String& /* identifier */, const String& /* source */) { }
+    virtual void didInsertAttachmentWithIdentifier(const String& /* identifier */, const String& /* source */, bool /* hasEnclosingImage */) { }
     virtual void didRemoveAttachmentWithIdentifier(const String&) { }
     virtual bool supportsClientSideAttachmentData() const { return false; }
 #endif
index ecd1e1c..177b7a2 100644 (file)
@@ -1,3 +1,63 @@
+2018-09-28  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        No DOM API to instantiate an attachment for an img element
+        https://bugs.webkit.org/show_bug.cgi?id=189934
+        <rdar://problem/44743222>
+
+        Reviewed by Ryosuke Niwa.
+
+        Makes some adjustments to support using _WKAttachment's file wrapper to change the contents of any image element
+        hosting the attachment in its shadow root. To do this, we add some plumbing to allow the UI process to update an
+        attachment element's enclosing image with data from its file wrapper.
+
+        * UIProcess/API/APIAttachment.cpp:
+        (API::Attachment::isEmpty const):
+        (API::Attachment::enclosingImageData const):
+
+        Helper method that creates a SharedBuffer representing image data for the attachment. Only returns a non-null
+        value for attachment elements that are enclosed within an image.
+
+        * UIProcess/API/APIAttachment.h:
+        * UIProcess/API/Cocoa/APIAttachmentCocoa.mm:
+        (API::Attachment::enclosingImageData const):
+        (API::Attachment::isEmpty const):
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _attachmentForIdentifier:]):
+
+        Add new SPI to request a _WKAttachment for a given unique identifier. Currently, this is only used for testing.
+
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+        * UIProcess/Cocoa/WebPageProxyCocoa.mm:
+        (WebKit::WebPageProxy::platformRegisterAttachment):
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::updateAttachmentAttributes):
+        (WebKit::WebPageProxy::registerAttachmentIdentifier):
+        (WebKit::WebPageProxy::didInsertAttachmentWithIdentifier):
+
+        Plumb whether or not the attachment is enclosed by an image from the web process to the UI process.
+
+        (WebKit::WebPageProxy::didRemoveAttachmentWithIdentifier):
+        (WebKit::WebPageProxy::didInsertAttachment): Deleted.
+        (WebKit::WebPageProxy::didRemoveAttachment):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/WebPageProxy.messages.in:
+        * WebProcess/WebCoreSupport/WebEditorClient.cpp:
+        (WebKit::WebEditorClient::registerAttachmentIdentifier):
+        (WebKit::WebEditorClient::didInsertAttachmentWithIdentifier):
+
+        Update attachment attributes after inserting an attachment. This ensures that an attachment that was created and
+        later inserted via script into the document will be synced with state in the UI process, if the client has
+        changed the contents of the attachment.
+
+        * WebProcess/WebCoreSupport/WebEditorClient.h:
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::updateAttachmentAttributes):
+
+        Plumb attachment data from the UI process to the web process.
+
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+
 2018-09-28  Chris Dumez  <cdumez@apple.com>
 
         Regression(r236512): http/tests/navigation/keyboard-events-during-provisional-navigation.html is flaky
index 64106c6..9cc0410 100644 (file)
@@ -71,6 +71,11 @@ void Attachment::invalidate()
 
 #if !PLATFORM(COCOA)
 
+bool Attachment::isEmpty() const
+{
+    return true;
+}
+
 WTF::String Attachment::mimeType() const
 {
     return m_contentType;
@@ -86,6 +91,11 @@ std::optional<uint64_t> Attachment::fileSizeForDisplay() const
     return std::nullopt;
 }
 
+RefPtr<WebCore::SharedBuffer> Attachment::enclosingImageData() const
+{
+    return nullptr;
+}
+
 #endif // !PLATFORM(COCOA)
 
 }
index 952f41b..b8fbbbe 100644 (file)
@@ -77,8 +77,14 @@ public:
     InsertionState insertionState() const { return m_insertionState; }
     void setInsertionState(InsertionState state) { m_insertionState = state; }
 
+    bool isEmpty() const;
+
+    RefPtr<WebCore::SharedBuffer> enclosingImageData() const;
     std::optional<uint64_t> fileSizeForDisplay() const;
 
+    void setHasEnclosingImage(bool hasEnclosingImage) { m_hasEnclosingImage = hasEnclosingImage; }
+    bool hasEnclosingImage() const { return m_hasEnclosingImage; }
+
 private:
     explicit Attachment(const WTF::String& identifier, WebKit::WebPageProxy&);
 
@@ -90,6 +96,7 @@ private:
     WTF::String m_contentType;
     WeakPtr<WebKit::WebPageProxy> m_webPage;
     InsertionState m_insertionState { InsertionState::NotInserted };
+    bool m_hasEnclosingImage { false };
 };
 
 } // namespace API
index 51740d7..31f7284 100644 (file)
@@ -27,6 +27,7 @@
 #import "APIAttachment.h"
 
 #import <WebCore/MIMETypeRegistry.h>
+#import <WebCore/SharedBuffer.h>
 #if PLATFORM(IOS)
 #import <MobileCoreServices/MobileCoreServices.h>
 #else
@@ -104,4 +105,24 @@ std::optional<uint64_t> Attachment::fileSizeForDisplay() const
     return [m_fileWrapper regularFileContents].length;
 }
 
+RefPtr<WebCore::SharedBuffer> Attachment::enclosingImageData() const
+{
+    if (!m_hasEnclosingImage)
+        return nullptr;
+
+    if (![m_fileWrapper isRegularFile])
+        return nullptr;
+
+    NSData *data = [m_fileWrapper regularFileContents];
+    if (!data)
+        return nullptr;
+
+    return WebCore::SharedBuffer::create(data);
+}
+
+bool Attachment::isEmpty() const
+{
+    return !m_fileWrapper;
+}
+
 } // namespace API
index 4aa5980..879f932 100644 (file)
@@ -4566,6 +4566,15 @@ WEBCORE_COMMAND(yankAndSelect)
 #endif
 }
 
+- (_WKAttachment *)_attachmentForIdentifier:(NSString *)identifier
+{
+#if ENABLE(ATTACHMENT_ELEMENT)
+    if (auto attachment = _page->attachmentForIdentifier(identifier))
+        return wrapper(attachment);
+#endif
+    return nil;
+}
+
 - (void)_pasteAsQuotation:(id)sender
 {
 #if PLATFORM(MAC)
index 7d25d3f..062f15e 100644 (file)
@@ -184,6 +184,7 @@ typedef NS_OPTIONS(NSUInteger, _WKRectEdge) {
 - (_WKAttachment *)_insertAttachmentWithFilename:(NSString *)filename contentType:(NSString *)contentType data:(NSData *)data options:(_WKAttachmentDisplayOptions *)options completion:(void(^)(BOOL success))completionHandler WK_API_DEPRECATED_WITH_REPLACEMENT("-_insertAttachmentWithFileWrapper:contentType:options:completion:", macosx(10.13.4, WK_MAC_TBA), ios(11.3, WK_IOS_TBA));
 - (_WKAttachment *)_insertAttachmentWithFileWrapper:(NSFileWrapper *)fileWrapper contentType:(NSString *)contentType options:(_WKAttachmentDisplayOptions *)options completion:(void(^)(BOOL success))completionHandler WK_API_DEPRECATED_WITH_REPLACEMENT("-_insertAttachmentWithFileWrapper:contentType:completion:", macosx(WK_MAC_TBA, WK_MAC_TBA), ios(WK_IOS_TBA, WK_IOS_TBA));
 - (_WKAttachment *)_insertAttachmentWithFileWrapper:(NSFileWrapper *)fileWrapper contentType:(NSString *)contentType completion:(void(^)(BOOL success))completionHandler WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+- (_WKAttachment *)_attachmentForIdentifier:(NSString *)identifier WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 
 - (IBAction)_pasteAsQuotation:(id)sender WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 
index db24bbc..0c73859 100644 (file)
@@ -164,6 +164,9 @@ void WebPageProxy::setDragCaretRect(const IntRect& dragCaretRect)
 
 void WebPageProxy::platformRegisterAttachment(Ref<API::Attachment>&& attachment, const String& preferredFileName, const IPC::DataReference& dataReference)
 {
+    if (dataReference.isEmpty())
+        return;
+
     auto buffer = SharedBuffer::create(dataReference.data(), dataReference.size());
     auto fileWrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:buffer->createNSData().autorelease()]);
     [fileWrapper setPreferredFilename:preferredFileName];
@@ -172,6 +175,9 @@ void WebPageProxy::platformRegisterAttachment(Ref<API::Attachment>&& attachment,
 
 void WebPageProxy::platformRegisterAttachment(Ref<API::Attachment>&& attachment, const String& filePath)
 {
+    if (!filePath)
+        return;
+
     auto fileWrapper = adoptNS([[NSFileWrapper alloc] initWithURL:[NSURL fileURLWithPath:filePath] options:0 error:nil]);
     attachment->setFileWrapper(fileWrapper.get());
 }
index b48f557..0a7db16 100644 (file)
@@ -78,6 +78,7 @@
 #include "PrintInfo.h"
 #include "SafeBrowsingResult.h"
 #include "ShareSheetCallbackID.h"
+#include "SharedBufferDataReference.h"
 #include "TextChecker.h"
 #include "TextCheckerState.h"
 #include "UIMessagePortChannelProvider.h"
@@ -7775,8 +7776,12 @@ void WebPageProxy::updateAttachmentAttributes(const API::Attachment& attachment,
         return;
     }
 
+    IPC::SharedBufferDataReference dataReference;
+    if (auto data = attachment.enclosingImageData())
+        dataReference = { *data };
+
     auto callbackID = m_callbacks.put(WTFMove(callback), m_process->throttler().backgroundActivityToken());
-    m_process->send(Messages::WebPage::UpdateAttachmentAttributes(attachment.identifier(), attachment.fileSizeForDisplay(), attachment.contentType(), attachment.fileName(), callbackID), m_pageID);
+    m_process->send(Messages::WebPage::UpdateAttachmentAttributes(attachment.identifier(), attachment.fileSizeForDisplay(), attachment.contentType(), attachment.fileName(), WTFMove(dataReference), callbackID), m_pageID);
 }
 
 void WebPageProxy::registerAttachmentIdentifierFromData(const String& identifier, const String& contentType, const String& preferredFileName, const IPC::DataReference& data)
@@ -7804,6 +7809,12 @@ void WebPageProxy::registerAttachmentIdentifierFromFilePath(const String& identi
     platformRegisterAttachment(WTFMove(attachment), filePath);
 }
 
+void WebPageProxy::registerAttachmentIdentifier(const String& identifier)
+{
+    if (!attachmentForIdentifier(identifier))
+        m_attachmentIdentifierToAttachmentMap.set(identifier, ensureAttachment(identifier));
+}
+
 void WebPageProxy::cloneAttachmentData(const String& fromIdentifier, const String& toIdentifier)
 {
     auto newAttachment = ensureAttachment(toIdentifier);
@@ -7845,10 +7856,15 @@ void WebPageProxy::platformCloneAttachment(Ref<API::Attachment>&&, Ref<API::Atta
 
 #endif
 
-void WebPageProxy::didInsertAttachmentWithIdentifier(const String& identifier, const String& source)
+void WebPageProxy::didInsertAttachmentWithIdentifier(const String& identifier, const String& source, bool hasEnclosingImage)
 {
     auto attachment = ensureAttachment(identifier);
-    didInsertAttachment(attachment.get(), source);
+    attachment->setHasEnclosingImage(hasEnclosingImage);
+    attachment->setInsertionState(API::Attachment::InsertionState::Inserted);
+    pageClient().didInsertAttachment(attachment.get(), source);
+
+    if (!attachment->isEmpty() && hasEnclosingImage)
+        updateAttachmentAttributes(attachment.get(), [] (auto) { });
 }
 
 void WebPageProxy::didRemoveAttachmentWithIdentifier(const String& identifier)
@@ -7857,12 +7873,6 @@ void WebPageProxy::didRemoveAttachmentWithIdentifier(const String& identifier)
         didRemoveAttachment(*attachment);
 }
 
-void WebPageProxy::didInsertAttachment(API::Attachment& attachment, const String& source)
-{
-    attachment.setInsertionState(API::Attachment::InsertionState::Inserted);
-    pageClient().didInsertAttachment(attachment, source);
-}
-
 void WebPageProxy::didRemoveAttachment(API::Attachment& attachment)
 {
     attachment.setInsertionState(API::Attachment::InsertionState::NotInserted);
index 74fe016..1f1d945 100644 (file)
@@ -1814,17 +1814,17 @@ private:
     void stopAllURLSchemeTasks();
 
 #if ENABLE(ATTACHMENT_ELEMENT)
-    void registerAttachmentIdentifierFromData(const String& identifier, const String& contentType, const String& preferredFileName, const IPC::DataReference&);
-    void registerAttachmentIdentifierFromFilePath(const String& identifier, const String& contentType, const String& filePath);
+    void registerAttachmentIdentifierFromData(const String&, const String& contentType, const String& preferredFileName, const IPC::DataReference&);
+    void registerAttachmentIdentifierFromFilePath(const String&, const String& contentType, const String& filePath);
+    void registerAttachmentIdentifier(const String&);
     void cloneAttachmentData(const String& fromIdentifier, const String& toIdentifier);
 
     void platformRegisterAttachment(Ref<API::Attachment>&&, const String& preferredFileName, const IPC::DataReference&);
     void platformRegisterAttachment(Ref<API::Attachment>&&, const String& filePath);
     void platformCloneAttachment(Ref<API::Attachment>&& fromAttachment, Ref<API::Attachment>&& toAttachment);
 
-    void didInsertAttachmentWithIdentifier(const String& identifier, const String& source);
+    void didInsertAttachmentWithIdentifier(const String& identifier, const String& source, bool hasEnclosingImage);
     void didRemoveAttachmentWithIdentifier(const String& identifier);
-    void didInsertAttachment(API::Attachment&, const String& source);
     void didRemoveAttachment(API::Attachment&);
     Ref<API::Attachment> ensureAttachment(const String& identifier);
     void invalidateAllAttachments();
index 1bb5c6e..3c44071 100644 (file)
@@ -534,8 +534,9 @@ messages -> WebPageProxy {
 #if ENABLE(ATTACHMENT_ELEMENT)
     RegisterAttachmentIdentifierFromData(String identifier, String contentType, String preferredFileName, IPC::SharedBufferDataReference data)
     RegisterAttachmentIdentifierFromFilePath(String identifier, String contentType, String filePath)
+    RegisterAttachmentIdentifier(String identifier)
     CloneAttachmentData(String fromIdentifier, String toIdentifier)
-    DidInsertAttachmentWithIdentifier(String identifier, String source)
+    DidInsertAttachmentWithIdentifier(String identifier, String source, bool hasEnclosingImage)
     DidRemoveAttachmentWithIdentifier(String identifier)
 #endif
 
index 91deb2a..9d083c4 100644 (file)
@@ -170,14 +170,19 @@ void WebEditorClient::registerAttachmentIdentifier(const String& identifier, con
     m_page->send(Messages::WebPageProxy::RegisterAttachmentIdentifierFromFilePath(identifier, contentType, filePath));
 }
 
+void WebEditorClient::registerAttachmentIdentifier(const String& identifier)
+{
+    m_page->send(Messages::WebPageProxy::RegisterAttachmentIdentifier(identifier));
+}
+
 void WebEditorClient::cloneAttachmentData(const String& fromIdentifier, const String& toIdentifier)
 {
     m_page->send(Messages::WebPageProxy::CloneAttachmentData(fromIdentifier, toIdentifier));
 }
 
-void WebEditorClient::didInsertAttachmentWithIdentifier(const String& identifier, const String& source)
+void WebEditorClient::didInsertAttachmentWithIdentifier(const String& identifier, const String& source, bool hasEnclosingImage)
 {
-    m_page->send(Messages::WebPageProxy::DidInsertAttachmentWithIdentifier(identifier, source));
+    m_page->send(Messages::WebPageProxy::DidInsertAttachmentWithIdentifier(identifier, source, hasEnclosingImage));
 }
 
 void WebEditorClient::didRemoveAttachmentWithIdentifier(const String& identifier)
index ef50e8a..c37f6ab 100644 (file)
@@ -60,10 +60,11 @@ private:
     bool shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*) final;
 
 #if ENABLE(ATTACHMENT_ELEMENT)
-    void registerAttachmentIdentifier(const String& identifier, const String& contentType, const String& preferredFileName, Ref<WebCore::SharedBuffer>&&) final;
-    void registerAttachmentIdentifier(const String& identifier, const String& contentType, const String& filePath) final;
+    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 cloneAttachmentData(const String& fromIdentifier, const String& toIdentifier) final;
-    void didInsertAttachmentWithIdentifier(const String& identifier, const String& source) final;
+    void didInsertAttachmentWithIdentifier(const String& identifier, const String& source, bool hasEnclosingImage) final;
     void didRemoveAttachmentWithIdentifier(const String& identifier) final;
     bool supportsClientSideAttachmentData() const final { return true; }
 #endif
index 3a9e8d9..541fe8e 100644 (file)
@@ -6123,11 +6123,12 @@ void WebPage::insertAttachment(const String& identifier, std::optional<uint64_t>
     send(Messages::WebPageProxy::VoidCallback(callbackID));
 }
 
-void WebPage::updateAttachmentAttributes(const String& identifier, std::optional<uint64_t>&& fileSize, const String& contentType, const String& fileName, CallbackID callbackID)
+void WebPage::updateAttachmentAttributes(const String& identifier, std::optional<uint64_t>&& fileSize, const String& contentType, const String& fileName, const IPC::DataReference& enclosingImageData, CallbackID callbackID)
 {
     if (auto attachment = attachmentElementWithIdentifier(identifier)) {
         attachment->document().updateLayout();
         attachment->updateAttributes(WTFMove(fileSize), contentType, fileName);
+        attachment->updateEnclosingImageWithData(contentType, SharedBuffer::create(enclosingImageData.data(), enclosingImageData.size()));
     }
     send(Messages::WebPageProxy::VoidCallback(callbackID));
 }
index e709fec..989b55b 100644 (file)
@@ -1081,7 +1081,7 @@ public:
     
 #if ENABLE(ATTACHMENT_ELEMENT)
     void insertAttachment(const String& identifier, std::optional<uint64_t>&& fileSize, const String& fileName, const String& contentType, CallbackID);
-    void updateAttachmentAttributes(const String& identifier, std::optional<uint64_t>&& fileSize, const String& contentType, const String& fileName, CallbackID);
+    void updateAttachmentAttributes(const String& identifier, std::optional<uint64_t>&& fileSize, const String& contentType, const String& fileName, const IPC::DataReference& enclosingImageData, CallbackID);
 #endif
 
 #if ENABLE(APPLICATION_MANIFEST)
index 8ddaaf3..4662411 100644 (file)
@@ -514,7 +514,7 @@ messages -> WebPage LegacyReceiver {
 
 #if ENABLE(ATTACHMENT_ELEMENT)
     InsertAttachment(String identifier, std::optional<uint64_t> fileSize, String fileName, String contentType, WebKit::CallbackID callbackID)
-    UpdateAttachmentAttributes(String identifier, std::optional<uint64_t> fileSize, String contentType, String fileName, WebKit::CallbackID callbackID)
+    UpdateAttachmentAttributes(String identifier, std::optional<uint64_t> fileSize, String contentType, String fileName, IPC::SharedBufferDataReference enclosingImageData, WebKit::CallbackID callbackID)
 #endif
 
 #if ENABLE(APPLICATION_MANIFEST)
index 9a9338f..92fa572 100644 (file)
@@ -1,3 +1,29 @@
+2018-09-28  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        No DOM API to instantiate an attachment for an img element
+        https://bugs.webkit.org/show_bug.cgi?id=189934
+        <rdar://problem/44743222>
+
+        Reviewed by Ryosuke Niwa.
+
+        Adds 3 new API tests in WKAttachmentTests:
+
+        `AddAttachmentToConnectedImageElement` verifies that an image element that's already in the document can gain an
+        attachment element via `HTMLAttachmentElement.getAttachmentIdentifier`.
+
+        `ChangeFileWrapperForPastedImage` verifies that an image that has been pasted produces a _WKAttachment in the UI
+        process, and changing the file wrapper of that _WKAttachment changes the pasted image.
+
+        `ConnectImageWithAttachmentToDocument` verifies that script can create an image element, ensure that it has an
+        attachment, and set a file wrapper for the generated _WKAttachment. Connecting the image to the document should
+        then result in an image element with the contents of the _WKAttachment's file wrapper.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm:
+        (-[TestWKWebView imageElementSize]):
+        (-[TestWKWebView waitForImageElementSizeToBecome:]):
+        (TestWebKitAPI::TEST):
+        (-[TestWKWebView waitForAttachmentElementSizeToBecome:]): Deleted.
+
 2018-09-28  Chris Dumez  <cdumez@apple.com>
 
         Regression(r236512): http/tests/navigation/keyboard-events-during-provisional-navigation.html is flaky
index 2c362a4..2a5fcd3 100644 (file)
@@ -305,10 +305,22 @@ static NSData *testPDFData()
     return size;
 }
 
-- (void)waitForAttachmentElementSizeToBecome:(CGSize)expectedSize
+- (CGSize)imageElementSize
+{
+    __block CGSize size;
+    __block bool doneEvaluatingScript = false;
+    [self evaluateJavaScript:@"r = document.querySelector('img').getBoundingClientRect(); [r.width, r.height]" completionHandler:^(NSArray<NSNumber *> *sizeResult, NSError *) {
+        size = CGSizeMake(sizeResult.firstObject.floatValue, sizeResult.lastObject.floatValue);
+        doneEvaluatingScript = true;
+    }];
+    TestWebKitAPI::Util::run(&doneEvaluatingScript);
+    return size;
+}
+
+- (void)waitForImageElementSizeToBecome:(CGSize)expectedSize
 {
     while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) {
-        if (CGSizeEqualToSize(self.attachmentElementSize, expectedSize))
+        if (CGSizeEqualToSize(self.imageElementSize, expectedSize))
             break;
     }
 }
@@ -907,7 +919,7 @@ TEST(WKAttachmentTests, RemoveNewlinesBeforePastedImage)
     auto size = platformImageWithData([attachment info].data).size;
     EXPECT_EQ(215., size.width);
     EXPECT_EQ(174., size.height);
-    EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').webkitAttachmentIdentifier"]);
+    EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"]);
 
     [webView stringByEvaluatingJavaScript:@"getSelection().collapse(document.body, 0)"];
     {
@@ -993,7 +1005,7 @@ TEST(WKAttachmentTests, InsertPastedAttributedStringContainingImage)
 
     [attachment expectRequestedDataToBe:testImageData()];
     EXPECT_WK_STREQ("Lorem ipsum  dolor sit amet.", [webView stringByEvaluatingJavaScript:@"document.body.textContent"]);
-    EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').webkitAttachmentIdentifier"]);
+    EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"]);
 
     {
         ObserveAttachmentUpdatesForScope observer(webView.get());
@@ -1039,7 +1051,7 @@ TEST(WKAttachmentTests, InsertPastedAttributedStringContainingMultipleAttachment
     EXPECT_WK_STREQ("application/octet-stream", zipAttachmentType);
 #endif
 
-    EXPECT_WK_STREQ([imageAttachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').webkitAttachmentIdentifier"]);
+    EXPECT_WK_STREQ([imageAttachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"]);
     EXPECT_WK_STREQ([pdfAttachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].uniqueIdentifier"]);
     EXPECT_WK_STREQ([zipAttachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].uniqueIdentifier"]);
 
@@ -1163,7 +1175,7 @@ TEST(WKAttachmentTests, InjectedBundleReplaceURLsWhenPastingAttributedString)
     }
     [webView expectElementTagsInOrder:@[ @"IMG", @"ATTACHMENT", @"ATTACHMENT" ]];
     EXPECT_WK_STREQ("cid:foo-bar", [webView valueOfAttribute:@"src" forQuerySelector:@"img"]);
-    EXPECT_WK_STREQ(@"", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('img')[0].webkitAttachmentIdentifier"]);
+    EXPECT_WK_STREQ(@"", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('img')[0].attachmentIdentifier"]);
 }
 
 TEST(WKAttachmentTests, InjectedBundleReplaceURLWhenPastingImage)
@@ -1348,6 +1360,77 @@ TEST(WKAttachmentTests, PasteWebArchiveContainingImages)
     observer.expectAttachmentUpdates(@[ ], @[ pngAttachment.get(), gifAttachment.get() ]);
 }
 
+TEST(WKAttachmentTests, ChangeFileWrapperForPastedImage)
+{
+    platformCopyPNG();
+    auto webView = webViewForTestingAttachments();
+
+    ObserveAttachmentUpdatesForScope observer(webView.get());
+    [webView paste:nil];
+    [webView waitForImageElementSizeToBecome:CGSizeMake(215, 174)];
+
+    auto attachment = retainPtr(observer.observer().inserted.firstObject);
+    auto originalImageData = retainPtr([attachment info].fileWrapper);
+    EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"HTMLAttachmentElement.getAttachmentIdentifier(document.querySelector('img'))"]);
+
+    auto newImage = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testGIFData()]);
+    [newImage setPreferredFilename:@"foo.gif"];
+    [attachment synchronouslySetFileWrapper:newImage.get() newContentType:nil error:nil];
+    [webView waitForImageElementSizeToBecome:CGSizeMake(52, 64)];
+
+    [attachment synchronouslySetFileWrapper:originalImageData.get() newContentType:@"image/png" error:nil];
+    [webView waitForImageElementSizeToBecome:CGSizeMake(215, 174)];
+}
+
+TEST(WKAttachmentTests, AddAttachmentToConnectedImageElement)
+{
+    auto webView = webViewForTestingAttachments();
+    [webView _synchronouslyExecuteEditCommand:@"InsertHTML" argument:@"<img></img>"];
+
+    ObserveAttachmentUpdatesForScope observer(webView.get());
+    NSString *attachmentIdentifier = [webView stringByEvaluatingJavaScript:@"HTMLAttachmentElement.getAttachmentIdentifier(document.querySelector('img'))"];
+    auto attachment = retainPtr([webView _attachmentForIdentifier:attachmentIdentifier]);
+    EXPECT_WK_STREQ(attachmentIdentifier, [attachment uniqueIdentifier]);
+    EXPECT_WK_STREQ(attachmentIdentifier, [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"]);
+    observer.expectAttachmentUpdates(@[ ], @[ attachment.get() ]);
+
+    auto firstImage = adoptNS([[NSFileWrapper alloc] initWithURL:testImageFileURL() options:0 error:nil]);
+    auto secondImage = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testGIFData()]);
+    [secondImage setPreferredFilename:@"foo.gif"];
+
+    [attachment synchronouslySetFileWrapper:firstImage.get() newContentType:@"image/png" error:nil];
+    [webView waitForImageElementSizeToBecome:CGSizeMake(215, 174)];
+
+    [attachment synchronouslySetFileWrapper:secondImage.get() newContentType:(__bridge NSString *)kUTTypeGIF error:nil];
+    [webView waitForImageElementSizeToBecome:CGSizeMake(52, 64)];
+
+    [attachment synchronouslySetFileWrapper:firstImage.get() newContentType:nil error:nil];
+    [webView waitForImageElementSizeToBecome:CGSizeMake(215, 174)];
+}
+
+TEST(WKAttachmentTests, ConnectImageWithAttachmentToDocument)
+{
+    auto webView = webViewForTestingAttachments();
+    ObserveAttachmentUpdatesForScope observer(webView.get());
+
+    NSString *identifier = [webView stringByEvaluatingJavaScript:@"image = document.createElement('img'); HTMLAttachmentElement.getAttachmentIdentifier(image)"];
+    auto image = adoptNS([[NSFileWrapper alloc] initWithURL:testImageFileURL() options:0 error:nil]);
+    auto attachment = retainPtr([webView _attachmentForIdentifier:identifier]);
+    [attachment synchronouslySetFileWrapper:image.get() newContentType:nil error:nil];
+    EXPECT_FALSE([attachment isConnected]);
+    observer.expectAttachmentUpdates(@[ ], @[ ]);
+
+    [webView evaluateJavaScript:@"document.body.appendChild(image)" completionHandler:nil];
+    [webView waitForImageElementSizeToBecome:CGSizeMake(215, 174)];
+    EXPECT_TRUE([attachment isConnected]);
+    observer.expectAttachmentUpdates(@[ ], @[ attachment.get() ]);
+
+    auto newImage = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testGIFData()]);
+    [newImage setPreferredFilename:@"test.gif"];
+    [attachment synchronouslySetFileWrapper:newImage.get() newContentType:nil error:nil];
+    [webView waitForImageElementSizeToBecome:CGSizeMake(52, 64)];
+}
+
 #pragma mark - Platform-specific tests
 
 #if PLATFORM(MAC)
@@ -1373,7 +1456,7 @@ TEST(WKAttachmentTestsMac, InsertPastedFileURLsAsAttachments)
     EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').getAttribute('type')"]);
     EXPECT_WK_STREQ("test.pdf", [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').getAttribute('title')"]);
 
-    NSString *imageAttachmentIdentifier = [webView stringByEvaluatingJavaScript:@"document.querySelector('img').webkitAttachmentIdentifier"];
+    NSString *imageAttachmentIdentifier = [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"];
     if ([testImageData() isEqualToData:[insertedAttachments firstObject].info.data])
         EXPECT_WK_STREQ([insertedAttachments firstObject].uniqueIdentifier, imageAttachmentIdentifier);
     else
@@ -1417,7 +1500,7 @@ TEST(WKAttachmentTestsMac, InsertDroppedFilePromisesAsAttachments)
             EXPECT_WK_STREQ("application/pdf", attachment.info.contentType);
         else if ([testImageData() isEqualToData:attachment.info.data]) {
             EXPECT_WK_STREQ("image/png", attachment.info.contentType);
-            EXPECT_WK_STREQ(attachment.uniqueIdentifier, [webView stringByEvaluatingJavaScript:@"document.querySelector('img').webkitAttachmentIdentifier"]);
+            EXPECT_WK_STREQ(attachment.uniqueIdentifier, [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"]);
         }
     }
 
@@ -1467,7 +1550,7 @@ TEST(WKAttachmentTestsIOS, InsertDroppedImageAsAttachment)
     EXPECT_EQ(0U, [dragAndDropSimulator removedAttachments].count);
     auto attachment = retainPtr([dragAndDropSimulator insertedAttachments].firstObject);
     [attachment expectRequestedDataToBe:testImageData()];
-    EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').webkitAttachmentIdentifier"]);
+    EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"]);
 
     {
         ObserveAttachmentUpdatesForScope observer(webView.get());
@@ -1495,7 +1578,7 @@ TEST(WKAttachmentTestsIOS, InsertDroppedAttributedStringContainingAttachment)
     auto size = platformImageWithData([attachment info].data).size;
     EXPECT_EQ(215., size.width);
     EXPECT_EQ(174., size.height);
-    EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').webkitAttachmentIdentifier"]);
+    EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"]);
 
     {
         ObserveAttachmentUpdatesForScope observer(webView.get());