[Attachment Support] Support dragging attachment elements out as files on macOS
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 22 Aug 2018 21:35:02 +0000 (21:35 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 22 Aug 2018 21:35:02 +0000 (21:35 +0000)
https://bugs.webkit.org/show_bug.cgi?id=181294
<rdar://problem/36298801>

Reviewed by Tim Horton.

Source/WebCore:

Serialize a dragged attachment element as a web archive on macOS. This allows us to move attachment elements
around a document by dragging, without performing a file upload upon every drop. Ideally, we should do this on
iOS as well, but this currently causes attachment data to go missing; further investigation to fix this for iOS
is tracked in <https://bugs.webkit.org/show_bug.cgi?id=181514>.

Tests:  WKAttachmentTestsMac.DragAttachmentAsFilePromise
        WKAttachmentTests.MoveAttachmentElementAsIconByDragging

* editing/cocoa/EditorCocoa.mm:
(WebCore::Editor::getPasteboardTypesAndDataForAttachment):

Source/WebKit:

Add support for dragging attachment elements on macOS by writing promised files to drag pasteboard. See changes
below for more details.

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView filePromiseProvider:fileNameForType:]):
(-[WKWebView filePromiseProvider:writePromiseToURL:completionHandler:]):
(-[WKWebView draggingSession:sourceOperationMaskForDraggingContext:]):
(-[WKWebView draggingSession:endedAtPoint:operation:]):
* UIProcess/API/mac/WKView.mm:
(-[WKView filePromiseProvider:fileNameForType:]):
(-[WKView filePromiseProvider:writePromiseToURL:completionHandler:]):
(-[WKView draggingSession:sourceOperationMaskForDraggingContext:]):
(-[WKView draggingSession:endedAtPoint:operation:]):

Plumb NSFilePromiseProviderDelegate and NSDraggingSource method implementations to WebViewImpl.

* UIProcess/Cocoa/WebViewImpl.h:
* UIProcess/Cocoa/WebViewImpl.mm:
(-[WKPromisedAttachmentContext initWithAttachmentInfo:]):
(-[WKPromisedAttachmentContext blobURL]):
(-[WKPromisedAttachmentContext filename]):
(-[WKPromisedAttachmentContext attachmentIdentifier]):

Add an object that contains the information needed to deliver a dragged attachment element's data via
NSFilePromiseProvider. This is stored as the userInfo of the NSFilePromiseProvider created upon drag start.

(WebKit::WebViewImpl::draggedImage):
(WebKit::WebViewImpl::sendDragEndToPage):

Add a helper method to handle cleanup after the dragging has finished, and call it from -draggedImage:… and
-draggingSessionEnded:…. The latter is only triggered in the where -beginDraggingSessionWithItems:… is used,
which currently only happens when dragging attachment elements.

(WebKit::WebViewImpl::fileNameForFilePromiseProvider):
(WebKit::webKitUnknownError):
(WebKit::WebViewImpl::writeToURLForFilePromiseProvider):

Deliver either NSFileWrapper data to the destination URL (in the case where an attachment identifier is known
and the corresponding API::Attachment is backed by a file wrapper), or save the contents of the blob URL to the
destination.

(WebKit::WebViewImpl::dragSourceOperationMask):
(WebKit::WebViewImpl::draggingSessionEnded):
(WebKit::WebViewImpl::startDrag):

Tools:

Add DragAndDropSimulator support for intercepting calls to -beginDraggingSessionWithitems:event:source:. This
enables us to write API tests for macOS that exercise the attachment SPI in combination with dragging attachment
elements.

* TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm:
(-[TestWKWebView attachmentElementMidPoint]):

Add a helper method local to this test suite that grabs the midpoint (in client coordinates) or the first
attachment element in the document.

(TestWebKitAPI::TEST):

Add a new API test to verify that dragging an attachment element on macOS produces file providers which may be
used to write attachment data to a path on disk. Additionally, refactor an existing API test,
MoveAttachmentElementAsIconByDragging, so that it runs on both iOS and macOS, to test the ability to move
attachment elements around in a document by using drag and drop.

* TestWebKitAPI/cocoa/DragAndDropSimulator.h:
* TestWebKitAPI/mac/DragAndDropSimulatorMac.mm:
(-[DragAndDropTestWKWebView beginDraggingSessionWithItems:event:source:]):
(-[DragAndDropSimulator initWithWebViewFrame:configuration:]):
(-[DragAndDropSimulator dealloc]):
(-[DragAndDropSimulator runFrom:to:]):
(-[DragAndDropSimulator beginDraggingSessionInWebView:withItems:source:]):

Begin a drag session and kick off the -continueDragSession loop. Unlike -performDragInWebView:…, which spins
the main runloop until dragging ends, this version returns execution to the web view and schedules dragging
updates asynchronously. This matches AppKit behavior.

(-[DragAndDropSimulator continueDragSession]):

Increment the dragging progress amount, send a drag update to the web view, and continue scheduling calls to
itself until the progress reaches 1.

(-[DragAndDropSimulator performDragInWebView:atLocation:withImage:pasteboard:source:]):
(-[DragAndDropSimulator initializeDraggingInfo:dragImage:source:]):

Pull out common logic for creating a new TestDraggingInfo after starting a drag.

(-[DragAndDropSimulator insertedAttachments]):
(-[DragAndDropSimulator removedAttachments]):
(-[DragAndDropSimulator draggingSession]):
(-[DragAndDropSimulator receivePromisedFiles]):

Helper method to save promised files written to the pasteboard after a drag to the temporary directory. These
files are tracked by DragAndDropSimulator and automatically cleaned up after the test finishes.

(-[DragAndDropSimulator endDataTransfer]):

Add a method stub on macOS, so that MoveAttachmentElementAsIconByDragging can be made cross-platform.

(-[DragAndDropSimulator _webView:didInsertAttachment:withSource:]):
(-[DragAndDropSimulator _webView:didRemoveAttachment:]):

Implement method stubs to keep track of inserted or removed attachments while simulating a drag.

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

Source/WebCore/ChangeLog
Source/WebCore/editing/cocoa/EditorCocoa.mm
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit/UIProcess/API/mac/WKView.mm
Source/WebKit/UIProcess/Cocoa/WebViewImpl.h
Source/WebKit/UIProcess/Cocoa/WebViewImpl.mm
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm
Tools/TestWebKitAPI/cocoa/DragAndDropSimulator.h
Tools/TestWebKitAPI/mac/DragAndDropSimulatorMac.mm

index fb7c6db..743ae7d 100644 (file)
@@ -1,3 +1,22 @@
+2018-08-22  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [Attachment Support] Support dragging attachment elements out as files on macOS
+        https://bugs.webkit.org/show_bug.cgi?id=181294
+        <rdar://problem/36298801>
+
+        Reviewed by Tim Horton.
+
+        Serialize a dragged attachment element as a web archive on macOS. This allows us to move attachment elements
+        around a document by dragging, without performing a file upload upon every drop. Ideally, we should do this on
+        iOS as well, but this currently causes attachment data to go missing; further investigation to fix this for iOS
+        is tracked in <https://bugs.webkit.org/show_bug.cgi?id=181514>.
+
+        Tests:  WKAttachmentTestsMac.DragAttachmentAsFilePromise
+                WKAttachmentTests.MoveAttachmentElementAsIconByDragging
+
+        * editing/cocoa/EditorCocoa.mm:
+        (WebCore::Editor::getPasteboardTypesAndDataForAttachment):
+
 2018-08-22  Aditya Keerthi  <akeerthi@apple.com>
 
         [iOS] Add support for the inputmode attribute
index bc63453..6bc903b 100644 (file)
@@ -169,6 +169,17 @@ void Editor::getPasteboardTypesAndDataForAttachment(HTMLAttachmentElement& attac
     // display mode when dragging and dropping or cutting and pasting. For the time being, this is disabled because
     // inserting attachment elements from web archive data sometimes causes attachment data to be lost; this requires
     // further investigation.
+#if PLATFORM(MAC)
+    // On macOS, we currently write the attachment as a web archive; we can't do the same for iOS and remove the platform guard above
+    // quite yet without breaking drag moves. This investigation is tracked in <https://bugs.webkit.org/show_bug.cgi?id=181514>.
+    // See the above FIXME for more details.
+    if (auto archive = LegacyWebArchive::create(attachmentRange.ptr())) {
+        if (auto webArchiveData = archive->rawDataRepresentation()) {
+            outTypes.append(WebArchivePboardType);
+            outData.append(SharedBuffer::create(webArchiveData.get()));
+        }
+    }
+#endif
 }
 
 #endif
index 8eebd03..ba1603b 100644 (file)
@@ -1,3 +1,56 @@
+2018-08-22  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [Attachment Support] Support dragging attachment elements out as files on macOS
+        https://bugs.webkit.org/show_bug.cgi?id=181294
+        <rdar://problem/36298801>
+
+        Reviewed by Tim Horton.
+
+        Add support for dragging attachment elements on macOS by writing promised files to drag pasteboard. See changes
+        below for more details.
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView filePromiseProvider:fileNameForType:]):
+        (-[WKWebView filePromiseProvider:writePromiseToURL:completionHandler:]):
+        (-[WKWebView draggingSession:sourceOperationMaskForDraggingContext:]):
+        (-[WKWebView draggingSession:endedAtPoint:operation:]):
+        * UIProcess/API/mac/WKView.mm:
+        (-[WKView filePromiseProvider:fileNameForType:]):
+        (-[WKView filePromiseProvider:writePromiseToURL:completionHandler:]):
+        (-[WKView draggingSession:sourceOperationMaskForDraggingContext:]):
+        (-[WKView draggingSession:endedAtPoint:operation:]):
+
+        Plumb NSFilePromiseProviderDelegate and NSDraggingSource method implementations to WebViewImpl.
+
+        * UIProcess/Cocoa/WebViewImpl.h:
+        * UIProcess/Cocoa/WebViewImpl.mm:
+        (-[WKPromisedAttachmentContext initWithAttachmentInfo:]):
+        (-[WKPromisedAttachmentContext blobURL]):
+        (-[WKPromisedAttachmentContext filename]):
+        (-[WKPromisedAttachmentContext attachmentIdentifier]):
+
+        Add an object that contains the information needed to deliver a dragged attachment element's data via
+        NSFilePromiseProvider. This is stored as the userInfo of the NSFilePromiseProvider created upon drag start.
+
+        (WebKit::WebViewImpl::draggedImage):
+        (WebKit::WebViewImpl::sendDragEndToPage):
+
+        Add a helper method to handle cleanup after the dragging has finished, and call it from -draggedImage:… and
+        -draggingSessionEnded:…. The latter is only triggered in the where -beginDraggingSessionWithItems:… is used,
+        which currently only happens when dragging attachment elements.
+
+        (WebKit::WebViewImpl::fileNameForFilePromiseProvider):
+        (WebKit::webKitUnknownError):
+        (WebKit::WebViewImpl::writeToURLForFilePromiseProvider):
+
+        Deliver either NSFileWrapper data to the destination URL (in the case where an attachment identifier is known
+        and the corresponding API::Attachment is backed by a file wrapper), or save the contents of the blob URL to the
+        destination.
+
+        (WebKit::WebViewImpl::dragSourceOperationMask):
+        (WebKit::WebViewImpl::draggingSessionEnded):
+        (WebKit::WebViewImpl::startDrag):
+
 2018-08-22  Aditya Keerthi  <akeerthi@apple.com>
 
         [iOS] Add support for the inputmode attribute
index 3379b48..53ddf90 100644 (file)
@@ -201,6 +201,13 @@ Boolean _AXSWebAccessibilityEventsEnabled();
 #endif
 #endif
 
+#if PLATFORM(MAC) && ENABLE(DRAG_SUPPORT)
+
+@interface WKWebView () <NSFilePromiseProviderDelegate, NSDraggingSource>
+@end
+
+#endif
+
 static HashMap<WebKit::WebPageProxy*, WKWebView *>& pageToViewMap()
 {
     static NeverDestroyed<HashMap<WebKit::WebPageProxy*, WKWebView *>> map;
@@ -4079,6 +4086,30 @@ WEBCORE_COMMAND(yankAndSelect)
     [self _gestureEventWasNotHandledByWebCore:event];
 }
 
+#if ENABLE(DRAG_SUPPORT)
+
+- (NSString *)filePromiseProvider:(NSFilePromiseProvider *)filePromiseProvider fileNameForType:(NSString *)fileType
+{
+    return _impl->fileNameForFilePromiseProvider(filePromiseProvider, fileType);
+}
+
+- (void)filePromiseProvider:(NSFilePromiseProvider *)filePromiseProvider writePromiseToURL:(NSURL *)url completionHandler:(void (^)(NSError *error))completionHandler
+{
+    _impl->writeToURLForFilePromiseProvider(filePromiseProvider, url, completionHandler);
+}
+
+- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
+{
+    return _impl->dragSourceOperationMask(session, context);
+}
+
+- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
+{
+    _impl->draggingSessionEnded(session, screenPoint, operation);
+}
+
+#endif // ENABLE(DRAG_SUPPORT)
+
 #endif // PLATFORM(MAC)
 
 #if HAVE(TOUCH_BAR)
index c0215c3..f776fdc 100644 (file)
@@ -66,6 +66,13 @@ using namespace WebCore;
 @end
 #endif
 
+#if ENABLE(DRAG_SUPPORT)
+
+@interface WKView () <NSFilePromiseProviderDelegate, NSDraggingSource>
+@end
+
+#endif
+
 @implementation WKView
 
 #if WK_API_ENABLED
@@ -1084,6 +1091,30 @@ Some other editing-related methods still unimplemented:
 
 #endif // HAVE(TOUCH_BAR)
 
+#if ENABLE(DRAG_SUPPORT)
+
+- (NSString *)filePromiseProvider:(NSFilePromiseProvider *)filePromiseProvider fileNameForType:(NSString *)fileType
+{
+    return _data->_impl->fileNameForFilePromiseProvider(filePromiseProvider, fileType);
+}
+
+- (void)filePromiseProvider:(NSFilePromiseProvider *)filePromiseProvider writePromiseToURL:(NSURL *)url completionHandler:(void (^)(NSError *error))completionHandler
+{
+    _data->_impl->writeToURLForFilePromiseProvider(filePromiseProvider, url, completionHandler);
+}
+
+- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
+{
+    return _data->_impl->dragSourceOperationMask(session, context);
+}
+
+- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
+{
+    _data->_impl->draggingSessionEnded(session, screenPoint, operation);
+}
+
+#endif // ENABLE(DRAG_SUPPORT)
+
 @end
 
 @implementation WKView (Private)
index 428aec3..a778b9a 100644 (file)
@@ -424,6 +424,12 @@ public:
     bool performDragOperation(id <NSDraggingInfo>);
     NSView *hitTestForDragTypes(CGPoint, NSSet *types);
     void registerDraggedTypes();
+
+    NSDragOperation dragSourceOperationMask(NSDraggingSession *, NSDraggingContext);
+    void draggingSessionEnded(NSDraggingSession *, NSPoint, NSDragOperation);
+
+    NSString *fileNameForFilePromiseProvider(NSFilePromiseProvider *, NSString *fileType);
+    void writeToURLForFilePromiseProvider(NSFilePromiseProvider *, NSURL *, void(^)(NSError *));
 #endif
 
     void startWindowDrag();
@@ -627,6 +633,10 @@ private:
     void handleRequestedCandidates(NSInteger sequenceNumber, NSArray<NSTextCheckingResult *> *candidates);
     void flushPendingMouseEventCallbacks();
 
+#if ENABLE(DRAG_SUPPORT)
+    void sendDragEndToPage(CGPoint endPoint, NSDragOperation);
+#endif
+
     WeakObjCPtr<NSView<WebViewImplDelegate>> m_view;
     std::unique_ptr<PageClient> m_pageClient;
     Ref<WebPageProxy> m_page;
index eaa6c20..4e5e251 100644 (file)
@@ -28,6 +28,7 @@
 
 #if PLATFORM(MAC)
 
+#import "APIAttachment.h"
 #import "APILegacyContextHistoryClient.h"
 #import "APINavigation.h"
 #import "AttributedString.h"
@@ -54,6 +55,7 @@
 #import "UndoOrRedo.h"
 #import "ViewGestureController.h"
 #import "WKBrowsingContextControllerInternal.h"
+#import "WKErrorInternal.h"
 #import "WKFullScreenWindowController.h"
 #import "WKImmediateActionController.h"
 #import "WKPrintingView.h"
@@ -83,6 +85,7 @@
 #import <WebCore/LoaderNSURLExtras.h>
 #import <WebCore/LocalizedStrings.h>
 #import <WebCore/PlatformEventFactoryMac.h>
+#import <WebCore/PromisedAttachmentInfo.h>
 #import <WebCore/TextAlternativeWithRange.h>
 #import <WebCore/TextUndoInsertionMarkupMac.h>
 #import <WebCore/WebActionDisablingCALayerDelegate.h>
@@ -874,6 +877,51 @@ static const NSUInteger orderedListSegment = 2;
 
 @end
 
+@interface WKPromisedAttachmentContext : NSObject {
+@private
+    RetainPtr<NSURL> _blobURL;
+    RetainPtr<NSString> _filename;
+    RetainPtr<NSString> _attachmentIdentifier;
+}
+
+- (instancetype)initWithAttachmentInfo:(const WebCore::PromisedAttachmentInfo&)info;
+
+@property (nonatomic, readonly) NSURL *blobURL;
+@property (nonatomic, readonly) NSString *filename;
+@property (nonatomic, readonly) NSString *attachmentIdentifier;
+
+@end
+
+@implementation WKPromisedAttachmentContext
+
+- (instancetype)initWithAttachmentInfo:(const WebCore::PromisedAttachmentInfo&)info
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _blobURL = info.blobURL;
+    _filename = info.filename;
+    _attachmentIdentifier = info.attachmentIdentifier;
+    return self;
+}
+
+- (NSURL *)blobURL
+{
+    return _blobURL.get();
+}
+
+- (NSString *)filename
+{
+    return _filename.get();
+}
+
+- (NSString *)attachmentIdentifier
+{
+    return _attachmentIdentifier.get();
+}
+
+@end
+
 namespace WebKit {
 
 NSTouchBar *WebViewImpl::makeTouchBar()
@@ -3660,7 +3708,12 @@ WKBrowsingContextController *WebViewImpl::browsingContextController()
 #endif // WK_API_ENABLED
 
 #if ENABLE(DRAG_SUPPORT)
-void WebViewImpl::draggedImage(NSImage *image, CGPoint endPoint, NSDragOperation operation)
+void WebViewImpl::draggedImage(NSImage *, CGPoint endPoint, NSDragOperation operation)
+{
+    sendDragEndToPage(endPoint, operation);
+}
+
+void WebViewImpl::sendDragEndToPage(CGPoint endPoint, NSDragOperation operation)
 {
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -3849,6 +3902,66 @@ void WebViewImpl::registerDraggedTypes()
     [m_view registerForDraggedTypes:[types allObjects]];
 }
 
+NSString *WebViewImpl::fileNameForFilePromiseProvider(NSFilePromiseProvider *provider, NSString *)
+{
+    id userInfo = provider.userInfo;
+    if (![userInfo isKindOfClass:[WKPromisedAttachmentContext class]])
+        return nil;
+
+    return [(WKPromisedAttachmentContext *)userInfo filename];
+}
+
+static NSError *webKitUnknownError()
+{
+#if WK_API_ENABLED
+    return [NSError errorWithDomain:WKErrorDomain code:WKErrorUnknown userInfo:nil];
+#else
+    return [NSError errorWithDomain:@"WKErrorDomain" code:1 userInfo:nil];
+#endif
+}
+
+void WebViewImpl::writeToURLForFilePromiseProvider(NSFilePromiseProvider *provider, NSURL *fileURL, void(^completionHandler)(NSError *))
+{
+    id userInfo = provider.userInfo;
+    if (![userInfo isKindOfClass:[WKPromisedAttachmentContext class]]) {
+        completionHandler(webKitUnknownError());
+        return;
+    }
+
+    WKPromisedAttachmentContext *info = (WKPromisedAttachmentContext *)userInfo;
+    auto attachment = m_page->attachmentForIdentifier(info.attachmentIdentifier);
+    if (NSFileWrapper *fileWrapper = attachment ? attachment->fileWrapper() : nil) {
+        NSError *attachmentWritingError = nil;
+        if ([fileWrapper writeToURL:fileURL options:0 originalContentsURL:nil error:&attachmentWritingError])
+            completionHandler(nil);
+        else
+            completionHandler(attachmentWritingError);
+        return;
+    }
+
+    WebCore::URL blobURL { info.blobURL };
+    if (blobURL.isEmpty()) {
+        completionHandler(webKitUnknownError());
+        return;
+    }
+
+    m_page->writeBlobToFilePath(blobURL, fileURL.path, [protectedCompletionHandler = makeBlockPtr(completionHandler)] (bool success) {
+        protectedCompletionHandler(success ? nil : webKitUnknownError());
+    });
+}
+
+NSDragOperation WebViewImpl::dragSourceOperationMask(NSDraggingSession *, NSDraggingContext context)
+{
+    if (context == NSDraggingContextOutsideApplication || m_page->currentDragIsOverFileInput())
+        return NSDragOperationCopy;
+    return NSDragOperationGeneric | NSDragOperationMove | NSDragOperationCopy;
+}
+
+void WebViewImpl::draggingSessionEnded(NSDraggingSession *, NSPoint endPoint, NSDragOperation operation)
+{
+    sendDragEndToPage(NSPointToCGPoint(endPoint), operation);
+}
+
 #endif // ENABLE(DRAG_SUPPORT)
 
 void WebViewImpl::startWindowDrag()
@@ -3873,14 +3986,34 @@ void WebViewImpl::startDrag(const WebCore::DragItem& item, const ShareableBitmap
 
     // The call below could release the view.
     auto protector = m_view.get();
+    auto clientDragLocation = item.dragLocationInWindowCoordinates;
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
     NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
 #pragma clang diagnostic pop
+
+    if (auto& attachmentInfo = item.promisedAttachmentInfo) {
+        auto provider = adoptNS([[NSFilePromiseProvider alloc] initWithFileType:attachmentInfo.contentType delegate:(id <NSFilePromiseProviderDelegate>)m_view.getAutoreleased()]);
+        [provider setUserInfo:[[[WKPromisedAttachmentContext alloc] initWithAttachmentInfo:attachmentInfo] autorelease]];
+        auto draggingItem = adoptNS([[NSDraggingItem alloc] initWithPasteboardWriter:provider.get()]);
+        [draggingItem setDraggingFrame:NSMakeRect(clientDragLocation.x(), clientDragLocation.y() - size.height(), size.width(), size.height()) contents:dragNSImage.get()];
+        [m_view beginDraggingSessionWithItems:@[draggingItem.get()] event:m_lastMouseDownEvent.get() source:(id <NSDraggingSource>)m_view.getAutoreleased()];
+
+        ASSERT(attachmentInfo.additionalTypes.size() == attachmentInfo.additionalData.size());
+        if (attachmentInfo.additionalTypes.size() == attachmentInfo.additionalData.size()) {
+            for (size_t index = 0; index < attachmentInfo.additionalTypes.size(); ++index) {
+                auto nsData = attachmentInfo.additionalData[index]->createNSData();
+                [pasteboard setData:nsData.get() forType:attachmentInfo.additionalTypes[index]];
+            }
+        }
+        m_page->didStartDrag();
+        return;
+    }
+
     [pasteboard setString:@"" forType:PasteboardTypes::WebDummyPboardType];
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
-    [m_view dragImage:dragNSImage.get() at:NSPointFromCGPoint(item.dragLocationInWindowCoordinates) offset:NSZeroSize event:m_lastMouseDownEvent.get() pasteboard:pasteboard source:m_view.getAutoreleased() slideBack:YES];
+    [m_view dragImage:dragNSImage.get() at:NSPointFromCGPoint(clientDragLocation) offset:NSZeroSize event:m_lastMouseDownEvent.get() pasteboard:pasteboard source:m_view.getAutoreleased() slideBack:YES];
 #pragma clang diagnostic pop
     m_page->didStartDrag();
 }
index 69be6a0..9321499 100644 (file)
@@ -1,3 +1,67 @@
+2018-08-22  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [Attachment Support] Support dragging attachment elements out as files on macOS
+        https://bugs.webkit.org/show_bug.cgi?id=181294
+        <rdar://problem/36298801>
+
+        Reviewed by Tim Horton.
+
+        Add DragAndDropSimulator support for intercepting calls to -beginDraggingSessionWithitems:event:source:. This
+        enables us to write API tests for macOS that exercise the attachment SPI in combination with dragging attachment
+        elements.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm:
+        (-[TestWKWebView attachmentElementMidPoint]):
+
+        Add a helper method local to this test suite that grabs the midpoint (in client coordinates) or the first
+        attachment element in the document.
+
+        (TestWebKitAPI::TEST):
+
+        Add a new API test to verify that dragging an attachment element on macOS produces file providers which may be
+        used to write attachment data to a path on disk. Additionally, refactor an existing API test,
+        MoveAttachmentElementAsIconByDragging, so that it runs on both iOS and macOS, to test the ability to move
+        attachment elements around in a document by using drag and drop.
+
+        * TestWebKitAPI/cocoa/DragAndDropSimulator.h:
+        * TestWebKitAPI/mac/DragAndDropSimulatorMac.mm:
+        (-[DragAndDropTestWKWebView beginDraggingSessionWithItems:event:source:]):
+        (-[DragAndDropSimulator initWithWebViewFrame:configuration:]):
+        (-[DragAndDropSimulator dealloc]):
+        (-[DragAndDropSimulator runFrom:to:]):
+        (-[DragAndDropSimulator beginDraggingSessionInWebView:withItems:source:]):
+
+        Begin a drag session and kick off the -continueDragSession loop. Unlike -performDragInWebView:…, which spins
+        the main runloop until dragging ends, this version returns execution to the web view and schedules dragging
+        updates asynchronously. This matches AppKit behavior.
+
+        (-[DragAndDropSimulator continueDragSession]):
+
+        Increment the dragging progress amount, send a drag update to the web view, and continue scheduling calls to
+        itself until the progress reaches 1.
+
+        (-[DragAndDropSimulator performDragInWebView:atLocation:withImage:pasteboard:source:]):
+        (-[DragAndDropSimulator initializeDraggingInfo:dragImage:source:]):
+
+        Pull out common logic for creating a new TestDraggingInfo after starting a drag.
+
+        (-[DragAndDropSimulator insertedAttachments]):
+        (-[DragAndDropSimulator removedAttachments]):
+        (-[DragAndDropSimulator draggingSession]):
+        (-[DragAndDropSimulator receivePromisedFiles]):
+
+        Helper method to save promised files written to the pasteboard after a drag to the temporary directory. These
+        files are tracked by DragAndDropSimulator and automatically cleaned up after the test finishes.
+
+        (-[DragAndDropSimulator endDataTransfer]):
+
+        Add a method stub on macOS, so that MoveAttachmentElementAsIconByDragging can be made cross-platform.
+
+        (-[DragAndDropSimulator _webView:didInsertAttachment:withSource:]):
+        (-[DragAndDropSimulator _webView:didRemoveAttachment:]):
+
+        Implement method stubs to keep track of inserted or removed attachments while simulating a drag.
+
 2018-08-22  Aditya Keerthi  <akeerthi@apple.com>
 
         [iOS] Add support for the inputmode attribute
index 89eea39..fb8d515 100644 (file)
@@ -265,6 +265,18 @@ static NSData *testPDFData()
     return attachment.autorelease();
 }
 
+- (CGPoint)attachmentElementMidPoint
+{
+    __block CGPoint midPoint;
+    __block bool doneEvaluatingScript = false;
+    [self evaluateJavaScript:@"r = document.querySelector('attachment').getBoundingClientRect(); [r.left + r.width / 2, r.top + r.height / 2]" completionHandler:^(NSArray<NSNumber *> *result, NSError *) {
+        midPoint = CGPointMake(result.firstObject.floatValue, result.lastObject.floatValue);
+        doneEvaluatingScript = true;
+    }];
+    TestWebKitAPI::Util::run(&doneEvaluatingScript);
+    return midPoint;
+}
+
 - (CGSize)attachmentElementSize
 {
     __block CGSize size;
@@ -1077,6 +1089,34 @@ TEST(WKAttachmentTests, InvalidateAttachmentsAfterWebProcessTermination)
     [htmlAttachment expectRequestedDataToBe:nil];
 }
 
+TEST(WKAttachmentTests, MoveAttachmentElementAsIconByDragging)
+{
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    [configuration _setAttachmentElementEnabled:YES];
+    auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:NSMakeRect(0, 0, 400, 400) configuration:configuration.get()]);
+    TestWKWebView *webView = [simulator webView];
+    [webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
+
+    auto data = retainPtr(testPDFData());
+    auto attachment = retainPtr([webView synchronouslyInsertAttachmentWithFilename:@"document.pdf" contentType:@"application/pdf" data:data.get()]);
+
+    [webView _executeEditCommand:@"InsertParagraph" argument:nil completion:nil];
+    [webView _executeEditCommand:@"InsertHTML" argument:@"<strong>text</strong>" completion:nil];
+    [webView _synchronouslyExecuteEditCommand:@"InsertParagraph" argument:nil];
+    [webView expectElementTag:@"ATTACHMENT" toComeBefore:@"STRONG"];
+
+    // Drag the attachment element to somewhere below the strong text.
+    [simulator runFrom:[webView attachmentElementMidPoint] to:CGPointMake(50, 300)];
+
+    EXPECT_EQ([simulator insertedAttachments].count, [simulator removedAttachments].count);
+    [attachment expectRequestedDataToBe:data.get()];
+    EXPECT_WK_STREQ("document.pdf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
+    EXPECT_WK_STREQ("application/pdf", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
+
+    [webView expectElementTag:@"STRONG" toComeBefore:@"ATTACHMENT"];
+    [simulator endDataTransfer];
+}
+
 #pragma mark - Platform-specific tests
 
 #if PLATFORM(MAC)
@@ -1128,15 +1168,14 @@ TEST(WKAttachmentTestsMac, InsertDroppedFilePromisesAsAttachments)
     [webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
     [simulator writePromisedFiles:@[ testPDFFileURL(), testImageFileURL() ]];
 
-    ObserveAttachmentUpdatesForScope observer(webView);
     [simulator runFrom:CGPointMake(0, 0) to:CGPointMake(50, 50)];
     while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) {
-        if (observer.observer().inserted.count == 2)
+        if ([simulator insertedAttachments].count == 2)
             break;
     }
     EXPECT_EQ(2, [[webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment').length"] intValue]);
 
-    auto insertedAttachments = retainPtr(observer.observer().inserted);
+    auto insertedAttachments = retainPtr([simulator insertedAttachments]);
     NSArray<NSData *> *expectedData = @[ testPDFData(), testImageData() ];
     for (_WKAttachment *attachment in insertedAttachments.get()) {
         EXPECT_GT(attachment.info.filePath.length, 0U);
@@ -1149,13 +1188,30 @@ TEST(WKAttachmentTestsMac, InsertDroppedFilePromisesAsAttachments)
 
     [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
     [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
-    NSArray<_WKAttachment *> *removedAttachments = [observer.observer() removed];
-    EXPECT_EQ(2U, removedAttachments.count);
+    auto removedAttachments = retainPtr([simulator removedAttachments]);
+    EXPECT_EQ(2U, [removedAttachments count]);
     EXPECT_EQ(0, [[webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment').length"] intValue]);
     EXPECT_TRUE([removedAttachments containsObject:[insertedAttachments firstObject]]);
     EXPECT_TRUE([removedAttachments containsObject:[insertedAttachments lastObject]]);
 }
 
+TEST(WKAttachmentTestsMac, DragAttachmentAsFilePromise)
+{
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    [configuration _setAttachmentElementEnabled:YES];
+    auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:NSMakeRect(0, 0, 400, 400) configuration:configuration.get()]);
+    TestWKWebView *webView = [simulator webView];
+    [webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
+
+    auto fileWrapper = adoptNS([[NSFileWrapper alloc] initWithURL:testPDFFileURL() options:0 error:nil]);
+    auto attachment = retainPtr([webView synchronouslyInsertAttachmentWithFileWrapper:fileWrapper.get() contentType:nil]);
+    [simulator runFrom:[webView attachmentElementMidPoint] to:CGPointMake(300, 300)];
+
+    NSArray<NSURL *> *urls = [simulator receivePromisedFiles];
+    EXPECT_EQ(1U, urls.count);
+    EXPECT_TRUE([[NSData dataWithContentsOfURL:urls.firstObject] isEqualToData:testPDFData()]);
+}
+
 #endif // PLATFORM(MAC)
 
 #if PLATFORM(IOS)
@@ -1367,34 +1423,6 @@ TEST(WKAttachmentTestsIOS, DragAttachmentInsertedAsData)
     [dragAndDropSimulator endDataTransfer];
 }
 
-TEST(WKAttachmentTestsIOS, MoveAttachmentElementAsIconByDragging)
-{
-    auto webView = webViewForTestingAttachments();
-    auto data = retainPtr(testPDFData());
-    RetainPtr<_WKAttachment> attachment;
-    {
-        ObserveAttachmentUpdatesForScope observer(webView.get());
-        attachment = [webView synchronouslyInsertAttachmentWithFilename:@"document.pdf" contentType:@"application/pdf" data:data.get()];
-        observer.expectAttachmentUpdates(@[], @[attachment.get()]);
-    }
-
-    [webView _executeEditCommand:@"InsertParagraph" argument:nil completion:nil];
-    [webView _executeEditCommand:@"InsertHTML" argument:@"<strong>text</strong>" completion:nil];
-    [webView _synchronouslyExecuteEditCommand:@"InsertParagraph" argument:nil];
-    [webView expectElementTag:@"ATTACHMENT" toComeBefore:@"STRONG"];
-
-    auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
-    [dragAndDropSimulator runFrom:CGPointMake(25, 25) to:CGPointMake(25, 425)];
-
-    attachment = [[dragAndDropSimulator insertedAttachments] firstObject];
-    [attachment expectRequestedDataToBe:data.get()];
-    EXPECT_WK_STREQ("document.pdf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
-    EXPECT_WK_STREQ("application/pdf", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
-
-    [webView expectElementTag:@"STRONG" toComeBefore:@"ATTACHMENT"];
-    [dragAndDropSimulator endDataTransfer];
-}
-
 #endif // PLATFORM(IOS)
 
 } // namespace TestWebKitAPI
index 2847f65..761a36f 100644 (file)
@@ -79,6 +79,7 @@ typedef NSDictionary<NSNumber *, NSValue *> *ProgressToCGPointValueMap;
 - (instancetype)initWithWebViewFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration;
 // The start location, end location, and locations of additional item requests are all in window coordinates.
 - (void)runFrom:(CGPoint)startLocation to:(CGPoint)endLocation;
+- (void)endDataTransfer;
 @property (nonatomic, readonly) NSArray<_WKAttachment *> *insertedAttachments;
 @property (nonatomic, readonly) NSArray<_WKAttachment *> *removedAttachments;
 @property (nonatomic, readonly) TestWKWebView *webView;
@@ -88,7 +89,6 @@ typedef NSDictionary<NSNumber *, NSValue *> *ProgressToCGPointValueMap;
 - (instancetype)initWithWebView:(TestWKWebView *)webView;
 - (void)runFrom:(CGPoint)startLocation to:(CGPoint)endLocation additionalItemRequestLocations:(ProgressToCGPointValueMap)additionalItemRequestLocations;
 - (void)waitForInputSession;
-- (void)endDataTransfer;
 
 @property (nonatomic, readonly) DragAndDropPhase phase;
 @property (nonatomic) BOOL allowsFocusToStartInputSession;
@@ -121,6 +121,7 @@ typedef NSDictionary<NSNumber *, NSValue *> *ProgressToCGPointValueMap;
 
 - (void)writePromisedFiles:(NSArray<NSURL *> *)fileURLs;
 - (void)writeFiles:(NSArray<NSURL *> *)fileURLs;
+- (NSArray<NSURL *> *)receivePromisedFiles;
 
 #endif // PLATFORM(MAC)
 
index 8c1199d..efd70bd 100644 (file)
@@ -37,7 +37,9 @@
 @class DragAndDropTestWKWebView;
 
 @interface DragAndDropSimulator ()
+- (void)beginDraggingSessionInWebView:(DragAndDropTestWKWebView *)webView withItems:(NSArray<NSDraggingItem *> *)items source:(id<NSDraggingSource>)source;
 - (void)performDragInWebView:(DragAndDropTestWKWebView *)webView atLocation:(NSPoint)viewLocation withImage:(NSImage *)image pasteboard:(NSPasteboard *)pasteboard source:(id)source;
+@property (nonatomic, readonly) NSDraggingSession *draggingSession;
 @end
 
 @interface DragAndDropTestWKWebView : TestWKWebView
     [_dragAndDropSimulator performDragInWebView:self atLocation:viewLocation withImage:image pasteboard:pboard source:sourceObj];
 }
 
+- (NSDraggingSession *)beginDraggingSessionWithItems:(NSArray<NSDraggingItem *> *)items event:(NSEvent *)event source:(id<NSDraggingSource>)source
+{
+    [_dragAndDropSimulator beginDraggingSessionInWebView:self withItems:items source:source];
+    return [_dragAndDropSimulator draggingSession];
+}
+
 - (void)waitForPendingMouseEvents
 {
     __block bool doneProcessMouseEvents = false;
@@ -85,10 +93,16 @@ static NSImage *defaultExternalDragImage()
     RetainPtr<NSPasteboard> _externalDragPasteboard;
     RetainPtr<NSImage> _externalDragImage;
     RetainPtr<NSArray<NSURL *>> _externalPromisedFiles;
+    RetainPtr<NSMutableArray<_WKAttachment *>> _insertedAttachments;
+    RetainPtr<NSMutableArray<_WKAttachment *>> _removedAttachments;
+    RetainPtr<NSMutableArray<NSURL *>> _filePromiseDestinationURLs;
+    RetainPtr<NSDraggingSession> _draggingSession;
+    RetainPtr<NSMutableArray<NSFilePromiseProvider *>> _filePromiseProviders;
     BlockPtr<void()> _willEndDraggingHandler;
     NSPoint _startLocationInWindow;
     NSPoint _endLocationInWindow;
     double _progress;
+    bool _doneWaitingForDraggingSession;
 }
 
 @synthesize currentDragOperation=_currentDragOperation;
@@ -103,11 +117,20 @@ static NSImage *defaultExternalDragImage()
 {
     if (self = [super init]) {
         _webView = adoptNS([[DragAndDropTestWKWebView alloc] initWithFrame:frame configuration:configuration ?: [[[WKWebViewConfiguration alloc] init] autorelease] simulator:self]);
+        _filePromiseDestinationURLs = adoptNS([NSMutableArray new]);
         [_webView setUIDelegate:self];
     }
     return self;
 }
 
+- (void)dealloc
+{
+    for (NSURL *url in _filePromiseDestinationURLs.get())
+        [[NSFileManager defaultManager] removeItemAtURL:url error:nil];
+
+    [super dealloc];
+}
+
 - (NSPoint)flipAboutXAxisInHostWindow:(NSPoint)point
 {
     return { point.x, NSHeight([[_webView hostWindow] frame]) - point.y };
@@ -129,11 +152,16 @@ static NSImage *defaultExternalDragImage()
 
 - (void)runFrom:(CGPoint)flippedStartLocation to:(CGPoint)flippedEndLocation
 {
+    _insertedAttachments = adoptNS([NSMutableArray new]);
+    _removedAttachments = adoptNS([NSMutableArray new]);
+    _doneWaitingForDraggingSession = true;
     _startLocationInWindow = [self flipAboutXAxisInHostWindow:flippedStartLocation];
     _endLocationInWindow = [self flipAboutXAxisInHostWindow:flippedEndLocation];
     _currentDragOperation = NSDragOperationNone;
     _draggingInfo = nil;
+    _draggingSession = nil;
     _progress = 0;
+    _filePromiseProviders = adoptNS([NSMutableArray new]);
 
     if (NSPasteboard *pasteboard = self.externalDragPasteboard) {
         NSPoint startLocationInView = [_webView convertPoint:_startLocationInWindow fromView:nil];
@@ -154,20 +182,76 @@ static NSImage *defaultExternalDragImage()
     [_webView mouseDragToPoint:[self locationInViewForCurrentProgress]];
     [_webView waitForPendingMouseEvents];
 
+    TestWebKitAPI::Util::run(&_doneWaitingForDraggingSession);
+
     [_webView mouseUpAtPoint:_endLocationInWindow];
     [_webView waitForPendingMouseEvents];
 }
 
+- (void)beginDraggingSessionInWebView:(DragAndDropTestWKWebView *)webView withItems:(NSArray<NSDraggingItem *> *)items source:(id<NSDraggingSource>)source
+{
+    NSMutableArray *pasteboardObjects = [NSMutableArray arrayWithCapacity:items.count];
+    NSMutableArray<NSString *> *promisedFileTypes = [NSMutableArray array];
+    for (NSDraggingItem *item in items) {
+        id pasteboardObject = item.item;
+        [pasteboardObjects addObject:pasteboardObject];
+        if ([pasteboardObject isKindOfClass:[NSFilePromiseProvider class]]) {
+            [_filePromiseProviders addObject:pasteboardObject];
+            [promisedFileTypes addObject:[(NSFilePromiseProvider *)pasteboardObject fileType]];
+        }
+    }
+
+    NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
+    [pasteboard clearContents];
+    [pasteboard writeObjects:pasteboardObjects];
+    if (promisedFileTypes.count) {
+        // Match AppKit behavior by writing legacy file promise types to the pasteboard as well.
+        [pasteboard setPropertyList:promisedFileTypes forType:NSFilesPromisePboardType];
+        [pasteboard addTypes:@[@"NSPromiseContentsPboardType", (NSString *)kPasteboardTypeFileURLPromise] owner:nil];
+    }
+
+    _draggingSession = adoptNS([[NSDraggingSession alloc] init]);
+    _doneWaitingForDraggingSession = false;
+    _initialDragImageLocationInView = items[0].draggingFrame.origin;
+    id dragImageContents = items[0].imageComponents.firstObject.contents;
+    [self initializeDraggingInfo:pasteboard dragImage:[dragImageContents isKindOfClass:[NSImage class]] ? dragImageContents : nil source:source];
+
+    _currentDragOperation = [_webView draggingEntered:_draggingInfo.get()];
+    [_webView waitForNextPresentationUpdate];
+    [self performSelector:@selector(continueDragSession) withObject:nil afterDelay:0];
+}
+
+- (void)continueDragSession
+{
+    _progress = std::min<double>(1, _progress + dragUpdateProgressIncrement);
+
+    if (_progress < 1) {
+        [_draggingInfo setDraggingLocation:[self locationInViewForCurrentProgress]];
+        _currentDragOperation = [_webView draggingUpdated:_draggingInfo.get()];
+        [_webView waitForNextPresentationUpdate];
+        [self performSelector:@selector(continueDragSession) withObject:nil afterDelay:0];
+        return;
+    }
+
+    [_draggingInfo setDraggingLocation:_endLocationInWindow];
+
+    if (_willEndDraggingHandler)
+        _willEndDraggingHandler();
+
+    if (_currentDragOperation != NSDragOperationNone && [_webView prepareForDragOperation:_draggingInfo.get()])
+        [_webView performDragOperation:_draggingInfo.get()];
+    else if (_currentDragOperation == NSDragOperationNone)
+        [_webView draggingExited:_draggingInfo.get()];
+    [_webView waitForNextPresentationUpdate];
+    [(id <NSDraggingSource>)_webView.get() draggingSession:_draggingSession.get() endedAtPoint:_endLocationInWindow operation:_currentDragOperation];
+
+    _doneWaitingForDraggingSession = true;
+}
+
 - (void)performDragInWebView:(DragAndDropTestWKWebView *)webView atLocation:(NSPoint)viewLocation withImage:(NSImage *)image pasteboard:(NSPasteboard *)pasteboard source:(id)source
 {
     _initialDragImageLocationInView = viewLocation;
-    _draggingInfo = adoptNS([[TestDraggingInfo alloc] initWithDragAndDropSimulator:self]);
-    [_draggingInfo setDraggedImage:image];
-    [_draggingInfo setDraggingPasteboard:pasteboard];
-    [_draggingInfo setDraggingSource:source];
-    [_draggingInfo setDraggingLocation:[self locationInViewForCurrentProgress]];
-    [_draggingInfo setDraggingSourceOperationMask:NSDragOperationEvery];
-    [_draggingInfo setNumberOfValidItemsForDrop:pasteboard.pasteboardItems.count];
+    [self initializeDraggingInfo:pasteboard dragImage:image source:source];
 
     _currentDragOperation = [_webView draggingEntered:_draggingInfo.get()];
     [_webView waitForNextPresentationUpdate];
@@ -196,14 +280,25 @@ static NSImage *defaultExternalDragImage()
     }
 }
 
+- (void)initializeDraggingInfo:(NSPasteboard *)pasteboard dragImage:(NSImage *)image source:(id)source
+{
+    _draggingInfo = adoptNS([[TestDraggingInfo alloc] initWithDragAndDropSimulator:self]);
+    [_draggingInfo setDraggedImage:image];
+    [_draggingInfo setDraggingPasteboard:pasteboard];
+    [_draggingInfo setDraggingSource:source];
+    [_draggingInfo setDraggingLocation:[self locationInViewForCurrentProgress]];
+    [_draggingInfo setDraggingSourceOperationMask:NSDragOperationEvery];
+    [_draggingInfo setNumberOfValidItemsForDrop:pasteboard.pasteboardItems.count];
+}
+
 - (NSArray<_WKAttachment *> *)insertedAttachments
 {
-    return @[ ];
+    return _insertedAttachments.get();
 }
 
 - (NSArray<_WKAttachment *> *)removedAttachments
 {
-    return @[ ];
+    return _removedAttachments.get();
 }
 
 - (TestWKWebView *)webView
@@ -231,6 +326,11 @@ static NSImage *defaultExternalDragImage()
     return _externalDragImage.get();
 }
 
+- (NSDraggingSession *)draggingSession
+{
+    return _draggingSession.get();
+}
+
 - (id <NSDraggingInfo>)draggingInfo
 {
     return _draggingInfo.get();
@@ -308,6 +408,45 @@ static BOOL getFilePathsAndTypeIdentifiers(NSArray<NSURL *> *fileURLs, NSArray<N
     [_externalDragPasteboard setPropertyList:paths forType:NSFilenamesPboardType];
 }
 
+- (NSArray<NSURL *> *)receivePromisedFiles
+{
+    auto destinationURLs = adoptNS([NSMutableArray new]);
+    for (NSFilePromiseProvider *provider in _filePromiseProviders.get()) {
+        if (!provider.delegate)
+            continue;
+
+        int suffix = 1;
+        NSString *baseFileName = [provider.delegate filePromiseProvider:provider fileNameForType:provider.fileType];
+        NSString *uniqueFileName = baseFileName;
+        while ([[NSFileManager defaultManager] fileExistsAtPath:[NSTemporaryDirectory() stringByAppendingPathComponent:uniqueFileName]])
+            uniqueFileName = [NSString stringWithFormat:@"%@ %d", baseFileName, ++suffix];
+
+        NSURL *destinationURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:uniqueFileName]];
+        __block bool done = false;
+        [provider.delegate filePromiseProvider:provider writePromiseToURL:destinationURL completionHandler:^(NSError *) {
+            done = true;
+        }];
+        TestWebKitAPI::Util::run(&done);
+        [destinationURLs addObject:destinationURL];
+        [_filePromiseDestinationURLs addObject:destinationURL];
+    }
+    return destinationURLs.autorelease();
+}
+
+- (void)endDataTransfer
+{
+}
+
+- (void)_webView:(WKWebView *)webView didInsertAttachment:(_WKAttachment *)attachment withSource:(NSString *)source
+{
+    [_insertedAttachments addObject:attachment];
+}
+
+- (void)_webView:(WKWebView *)webView didRemoveAttachment:(_WKAttachment *)attachment
+{
+    [_removedAttachments addObject:attachment];
+}
+
 @end
 
 #endif // ENABLE(DRAG_SUPPORT) && PLATFORM(MAC) && WK_API_ENABLED