[iOS DnD] For cross-app drags, 'drop' event handlers are never invoked if dataTransfe...
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 7 Jul 2017 21:59:25 +0000 (21:59 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 7 Jul 2017 21:59:25 +0000 (21:59 +0000)
https://bugs.webkit.org/show_bug.cgi?id=174219
<rdar://problem/32083177>

Reviewed by Ryosuke Niwa.

Source/WebCore:

Currently, in DragController.cpp, defaultOperationForDrag maps a drag source operation mask of
DragOperationGeneric to DragOperationMove across all platforms. However, on iOS, where cross-app drag moves do
not trigger a drop, this means drop handlers won't fire unless the dropEffect is explicitly set to copy.

To fix this, we introduce DragController::platformGenericDragOperation(), which returns DragOperationCopy on iOS
and DragOperationMove (the existing behavior) elsewhere. defaultOperationForDrag then maps a drag source
operation mask of DragOperationGeneric to platformGenericDragOperation().

Tests:  DataInteractionTests.ExternalSourceHTMLToUploadArea
        DataInteractionTests.ExternalSourceImageAndHTMLToUploadArea
        DataInteractionTests.ExternalSourceMoveOperationNotAllowed

* page/DragController.cpp:
(WebCore::DragController::platformGenericDragOperation):
(WebCore::defaultOperationForDrag):
* page/DragController.h:
* page/mac/DragControllerMac.mm:
(WebCore::DragController::platformGenericDragOperation):

Source/WebKit2:

Tweak some testing SPI to return a drop operation flag instead of whether or not the drop operation was not
UIDropOperationCancel.

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _simulateDataInteractionUpdated:]):
* UIProcess/API/Cocoa/WKWebViewPrivate.h:
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView _simulateDataInteractionUpdated:]):

Tools:

Add plumbing and support to mock the value of -allowsMoveOperation on the simulated UIDragDropSession objects.
Setting the DataInteractionSimulator's shouldAllowMoveOperation property to NO simulates a drag operation coming
in from another app out-of-process, for which move operations won't cause a drop to be performed in the first
place.

Also tweaks 2 existing unit tests regarding file uploads via JavaScript to simulate items coming in from a
different application, and adds a new test to check that if a drop area specifically requests a MOVE operation,
no action is taken when dropping.

* TestWebKitAPI/Tests/WebKit2Cocoa/file-uploading.html:
* TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
(TestWebKitAPI::TEST):
* TestWebKitAPI/ios/DataInteractionSimulator.h:
* TestWebKitAPI/ios/DataInteractionSimulator.mm:
(-[MockDragDropSession initWithItems:location:window:allowMove:]):
(-[MockDragDropSession allowsMoveOperation]):
(-[MockDataOperationSession initWithProviders:location:window:allowMove:]):
(-[MockDataInteractionSession initWithWindow:allowMove:]):
(-[DataInteractionSimulator initWithWebView:]):
(-[DataInteractionSimulator runFrom:to:]):
(-[DataInteractionSimulator _advanceProgress]):
(-[MockDragDropSession initWithItems:location:window:]): Deleted.
(-[MockDataOperationSession initWithProviders:location:window:]): Deleted.
(-[MockDataInteractionSession initWithWindow:]): Deleted.

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

14 files changed:
Source/WebCore/ChangeLog
Source/WebCore/page/DragController.cpp
Source/WebCore/page/DragController.h
Source/WebCore/page/mac/DragControllerMac.mm
Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit2/UIProcess/API/Cocoa/WKWebViewPrivate.h
Source/WebKit2/UIProcess/ios/WKContentViewInteraction.h
Source/WebKit2/UIProcess/ios/WKContentViewInteraction.mm
Tools/ChangeLog
Tools/TestWebKitAPI/Tests/WebKit2Cocoa/file-uploading.html
Tools/TestWebKitAPI/Tests/ios/DataInteractionTests.mm
Tools/TestWebKitAPI/ios/DataInteractionSimulator.h
Tools/TestWebKitAPI/ios/DataInteractionSimulator.mm

index 5ec39da423440fac61ab111d4ddd01ece6cfca4b..14a4c6681700bea3e68d10a96778c9c2d362d85c 100644 (file)
@@ -1,3 +1,30 @@
+2017-07-07  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS DnD] For cross-app drags, 'drop' event handlers are never invoked if dataTransfer.dropEffect is not set while dragging
+        https://bugs.webkit.org/show_bug.cgi?id=174219
+        <rdar://problem/32083177>
+
+        Reviewed by Ryosuke Niwa.
+
+        Currently, in DragController.cpp, defaultOperationForDrag maps a drag source operation mask of
+        DragOperationGeneric to DragOperationMove across all platforms. However, on iOS, where cross-app drag moves do
+        not trigger a drop, this means drop handlers won't fire unless the dropEffect is explicitly set to copy.
+
+        To fix this, we introduce DragController::platformGenericDragOperation(), which returns DragOperationCopy on iOS
+        and DragOperationMove (the existing behavior) elsewhere. defaultOperationForDrag then maps a drag source
+        operation mask of DragOperationGeneric to platformGenericDragOperation().
+
+        Tests:  DataInteractionTests.ExternalSourceHTMLToUploadArea
+                DataInteractionTests.ExternalSourceImageAndHTMLToUploadArea
+                DataInteractionTests.ExternalSourceMoveOperationNotAllowed
+
+        * page/DragController.cpp:
+        (WebCore::DragController::platformGenericDragOperation):
+        (WebCore::defaultOperationForDrag):
+        * page/DragController.h:
+        * page/mac/DragControllerMac.mm:
+        (WebCore::DragController::platformGenericDragOperation):
+
 2017-07-07  Devin Rousso  <drousso@apple.com>
 
         Web Inspector: Show all elements currently using a given CSS Canvas
index d73d384c6133656e7815c97491bbe38038dd5403..11b036cc7a67003f657cdd92fae43f7cbcab3c7c 100644 (file)
@@ -184,6 +184,15 @@ static RefPtr<DocumentFragment> documentFragmentFromDragData(const DragData& dra
     return nullptr;
 }
 
+#if !PLATFORM(IOS)
+
+DragOperation DragController::platformGenericDragOperation()
+{
+    return DragOperationMove;
+}
+
+#endif
+
 bool DragController::dragIsMove(FrameSelection& selection, const DragData& dragData)
 {
     const VisibleSelection& visibleSelection = selection.selection();
@@ -666,8 +675,10 @@ static DragOperation defaultOperationForDrag(DragOperation srcOpMask)
         return DragOperationCopy;
     if (srcOpMask == DragOperationNone)
         return DragOperationNone;
-    if (srcOpMask & DragOperationMove || srcOpMask & DragOperationGeneric)
+    if (srcOpMask & DragOperationMove)
         return DragOperationMove;
+    if (srcOpMask & DragOperationGeneric)
+        return DragController::platformGenericDragOperation();
     if (srcOpMask & DragOperationCopy)
         return DragOperationCopy;
     if (srcOpMask & DragOperationLink)
index 2b0becd3a3beb9f7fb7039fe19eec25a28d9ad28..fb901f3f1d5c2679f0460e7e786071216742f7e0 100644 (file)
@@ -55,6 +55,7 @@ struct DragState;
         ~DragController();
 
         static std::unique_ptr<DragController> create(Page&, DragClient&);
+        static DragOperation platformGenericDragOperation();
 
         DragClient& client() const { return m_client; }
 
index c46fc8f3ef3bba76d48dea64106f1ec0acee538f..add8b70668004c79ef08c64767ffb0d8370e90c1 100644 (file)
@@ -106,6 +106,13 @@ void DragController::cleanupAfterSystemDrag()
 
 #if ENABLE(DATA_INTERACTION)
 
+DragOperation DragController::platformGenericDragOperation()
+{
+    // On iOS, UIKit skips the -performDrop invocation altogether if MOVE is forbidden.
+    // Thus, if MOVE is not allowed in the drag source operation mask, fall back to only other allowable action, COPY.
+    return DragOperationCopy;
+}
+
 void DragController::updateSupportedTypeIdentifiersForDragHandlingMethod(DragHandlingMethod dragHandlingMethod, const DragData& dragData) const
 {
     Vector<String> supportedTypes;
index f00afe9f8c556781943aac9008d1e9d7ef54a772..55990e0ca05d820e9831a255fbc52159afbb3b27 100644 (file)
@@ -1,3 +1,21 @@
+2017-07-07  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS DnD] For cross-app drags, 'drop' event handlers are never invoked if dataTransfer.dropEffect is not set while dragging
+        https://bugs.webkit.org/show_bug.cgi?id=174219
+        <rdar://problem/32083177>
+
+        Reviewed by Ryosuke Niwa.
+
+        Tweak some testing SPI to return a drop operation flag instead of whether or not the drop operation was not
+        UIDropOperationCancel.
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _simulateDataInteractionUpdated:]):
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView _simulateDataInteractionUpdated:]):
+
 2017-07-07  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r219238, r219239, and r219241.
index c9acaf968d362c0b464d9a6f464a63bf305ea376..18a43a8a0035fe272f22d85470e93e3fa10507a4 100644 (file)
@@ -5628,12 +5628,12 @@ static WebCore::UserInterfaceLayoutDirection toUserInterfaceLayoutDirection(UISe
 #endif
 }
 
-- (BOOL)_simulateDataInteractionUpdated:(id)info
+- (NSUInteger)_simulateDataInteractionUpdated:(id)info
 {
 #if ENABLE(DATA_INTERACTION)
     return [_contentView _simulateDataInteractionUpdated:info];
 #else
-    return NO;
+    return 0;
 #endif
 }
 
index 7d2ec5bd31a4dcd1c0e8125dfa8a276f913e84af..9be9f0a4f79051f26482813481d6afd065a7195e 100644 (file)
@@ -352,7 +352,7 @@ typedef NS_ENUM(NSInteger, _WKImmediateActionType) {
 - (NSDictionary *)_propertiesOfLayerWithID:(unsigned long long)layerID WK_API_AVAILABLE(ios(WK_IOS_TBA));
 
 - (void)_simulateDataInteractionEntered:(id)info WK_API_AVAILABLE(ios(WK_IOS_TBA));
-- (BOOL)_simulateDataInteractionUpdated:(id)info WK_API_AVAILABLE(ios(WK_IOS_TBA));
+- (NSUInteger)_simulateDataInteractionUpdated:(id)info WK_API_AVAILABLE(ios(WK_IOS_TBA));
 - (void)_simulateDataInteractionPerformOperation:(id)info WK_API_AVAILABLE(ios(WK_IOS_TBA));
 - (void)_simulateDataInteractionEnded:(id)info WK_API_AVAILABLE(ios(WK_IOS_TBA));
 - (void)_simulateDataInteractionSessionDidEnd:(id)session WK_API_AVAILABLE(ios(WK_IOS_TBA));
index 32742368a80099eb757164ed8688045a9c0a78f2..b228036676fb307d5e90d48c479c5e9de7aa449c 100644 (file)
@@ -342,7 +342,7 @@ FOR_EACH_WKCONTENTVIEW_ACTION(DECLARE_WKCONTENTVIEW_ACTION_FOR_WEB_VIEW)
 - (void)_didChangeDataInteractionCaretRect:(CGRect)previousRect currentRect:(CGRect)rect;
 
 - (void)_simulateDataInteractionEntered:(id)info;
-- (BOOL)_simulateDataInteractionUpdated:(id)info;
+- (NSUInteger)_simulateDataInteractionUpdated:(id)info;
 - (void)_simulateDataInteractionPerformOperation:(id)info;
 - (void)_simulateDataInteractionEnded:(id)info;
 - (void)_simulateDataInteractionSessionDidEnd:(id)session;
index 7d9581c812eccff5a2e0e6edd321e6e4cb5d4462..e53465ed2ffe6dd3f3fa11b29cebae6b94b8b5ee 100644 (file)
@@ -4858,9 +4858,9 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
     [self dropInteraction:_dataOperation.get() sessionDidEnter:session];
 }
 
-- (BOOL)_simulateDataInteractionUpdated:(id)session
+- (NSUInteger)_simulateDataInteractionUpdated:(id)session
 {
-    return [self dropInteraction:_dataOperation.get() sessionDidUpdate:session].operation != UIDropOperationCancel;
+    return [self dropInteraction:_dataOperation.get() sessionDidUpdate:session].operation;
 }
 
 - (void)_simulateDataInteractionEnded:(id)session
index e1754e1d6b4afb97c7aae481f5c936c23505ef25..e91655e94f9d6a9ece65d35b32e71559ad63643e 100644 (file)
@@ -1,3 +1,36 @@
+2017-07-07  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS DnD] For cross-app drags, 'drop' event handlers are never invoked if dataTransfer.dropEffect is not set while dragging
+        https://bugs.webkit.org/show_bug.cgi?id=174219
+        <rdar://problem/32083177>
+
+        Reviewed by Ryosuke Niwa.
+
+        Add plumbing and support to mock the value of -allowsMoveOperation on the simulated UIDragDropSession objects.
+        Setting the DataInteractionSimulator's shouldAllowMoveOperation property to NO simulates a drag operation coming
+        in from another app out-of-process, for which move operations won't cause a drop to be performed in the first
+        place.
+
+        Also tweaks 2 existing unit tests regarding file uploads via JavaScript to simulate items coming in from a
+        different application, and adds a new test to check that if a drop area specifically requests a MOVE operation,
+        no action is taken when dropping.
+
+        * TestWebKitAPI/Tests/WebKit2Cocoa/file-uploading.html:
+        * TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/ios/DataInteractionSimulator.h:
+        * TestWebKitAPI/ios/DataInteractionSimulator.mm:
+        (-[MockDragDropSession initWithItems:location:window:allowMove:]):
+        (-[MockDragDropSession allowsMoveOperation]):
+        (-[MockDataOperationSession initWithProviders:location:window:allowMove:]):
+        (-[MockDataInteractionSession initWithWindow:allowMove:]):
+        (-[DataInteractionSimulator initWithWebView:]):
+        (-[DataInteractionSimulator runFrom:to:]):
+        (-[DataInteractionSimulator _advanceProgress]):
+        (-[MockDragDropSession initWithItems:location:window:]): Deleted.
+        (-[MockDataOperationSession initWithProviders:location:window:]): Deleted.
+        (-[MockDataInteractionSession initWithWindow:]): Deleted.
+
 2017-07-07  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r219238, r219239, and r219241.
index 67749587130f79dec0bcfb3c6bf30085ee3f4883..6f80d2ac4fe4144a3543dca7cf257dcf24a4adf4 100644 (file)
         sendUploadedFileTypesToApp(input.files);
     });
 
-    upload.addEventListener("dragenter", event => event.preventDefault());
-    upload.addEventListener("dragover", event => event.preventDefault());
+    upload.addEventListener("dragenter", event => {
+        event.preventDefault();
+        if (upload.dropEffect)
+            event.dataTransfer.dropEffect = upload.dropEffect;
+    });
+
+    upload.addEventListener("dragover", event => {
+        event.preventDefault();
+        if (upload.dropEffect)
+            event.dataTransfer.dropEffect = upload.dropEffect;
+    });
+
     upload.addEventListener("drop", event => {
         sendUploadedFileTypesToApp(event.dataTransfer.files);
         event.preventDefault();
index c18372cd59cda42c0222d6b234675419cf9d7081..aafa233c2a2d22b4795348f762b94bce92b55d63 100644 (file)
@@ -496,14 +496,15 @@ TEST(DataInteractionTests, ExternalSourceImageToFileInput)
 
 TEST(DataInteractionTests, ExternalSourceHTMLToUploadArea)
 {
-    RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
     [webView synchronouslyLoadTestPageNamed:@"file-uploading"];
 
-    RetainPtr<UIItemProvider> simulatedHTMLItemProvider = adoptNS([[UIItemProvider alloc] init]);
+    auto simulatedHTMLItemProvider = adoptNS([[UIItemProvider alloc] init]);
     NSData *htmlData = [@"<body contenteditable></body>" dataUsingEncoding:NSUTF8StringEncoding];
     [simulatedHTMLItemProvider registerDataRepresentationForTypeIdentifier:(NSString *)kUTTypeHTML withData:htmlData];
 
-    RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+    auto dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+    [dataInteractionSimulator setShouldAllowMoveOperation:NO];
     [dataInteractionSimulator setExternalItemProviders:@[ simulatedHTMLItemProvider.get() ]];
     [dataInteractionSimulator runFrom:CGPointMake(200, 300) to:CGPointMake(100, 300)];
 
@@ -511,6 +512,24 @@ TEST(DataInteractionTests, ExternalSourceHTMLToUploadArea)
     EXPECT_WK_STREQ("text/html", outputValue.UTF8String);
 }
 
+TEST(DataInteractionTests, ExternalSourceMoveOperationNotAllowed)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"file-uploading"];
+    [webView stringByEvaluatingJavaScript:@"upload.dropEffect = 'move'"];
+
+    auto simulatedHTMLItemProvider = adoptNS([[UIItemProvider alloc] init]);
+    NSData *htmlData = [@"<body contenteditable></body>" dataUsingEncoding:NSUTF8StringEncoding];
+    [simulatedHTMLItemProvider registerDataRepresentationForTypeIdentifier:(NSString *)kUTTypeHTML withData:htmlData];
+
+    auto dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+    [dataInteractionSimulator setShouldAllowMoveOperation:NO];
+    [dataInteractionSimulator setExternalItemProviders:@[ simulatedHTMLItemProvider.get() ]];
+    [dataInteractionSimulator runFrom:CGPointMake(200, 300) to:CGPointMake(100, 300)];
+
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"output.value"]);
+}
+
 TEST(DataInteractionTests, ExternalSourceZIPArchiveAndURLToSingleFileInput)
 {
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
@@ -591,22 +610,23 @@ TEST(DataInteractionTests, ExternalSourceImageAndHTMLToMultipleFileInput)
 
 TEST(DataInteractionTests, ExternalSourceImageAndHTMLToUploadArea)
 {
-    RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
     [webView synchronouslyLoadTestPageNamed:@"file-uploading"];
 
-    RetainPtr<UIItemProvider> simulatedImageItemProvider = adoptNS([[UIItemProvider alloc] init]);
+    auto simulatedImageItemProvider = adoptNS([[UIItemProvider alloc] init]);
     NSData *imageData = UIImageJPEGRepresentation(testIconImage(), 0.5);
     [simulatedImageItemProvider registerDataRepresentationForTypeIdentifier:(NSString *)kUTTypeJPEG withData:imageData];
 
-    RetainPtr<UIItemProvider> firstSimulatedHTMLItemProvider = adoptNS([[UIItemProvider alloc] init]);
+    auto firstSimulatedHTMLItemProvider = adoptNS([[UIItemProvider alloc] init]);
     NSData *firstHTMLData = [@"<body contenteditable></body>" dataUsingEncoding:NSUTF8StringEncoding];
     [firstSimulatedHTMLItemProvider registerDataRepresentationForTypeIdentifier:(NSString *)kUTTypeHTML withData:firstHTMLData];
 
-    RetainPtr<UIItemProvider> secondSimulatedHTMLItemProvider = adoptNS([[UIItemProvider alloc] init]);
+    auto secondSimulatedHTMLItemProvider = adoptNS([[UIItemProvider alloc] init]);
     NSData *secondHTMLData = [@"<html><body>hello world</body></html>" dataUsingEncoding:NSUTF8StringEncoding];
     [secondSimulatedHTMLItemProvider registerDataRepresentationForTypeIdentifier:(NSString *)kUTTypeHTML withData:secondHTMLData];
 
-    RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+    auto dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+    [dataInteractionSimulator setShouldAllowMoveOperation:NO];
     [dataInteractionSimulator setExternalItemProviders:@[ simulatedImageItemProvider.get(), firstSimulatedHTMLItemProvider.get(), secondSimulatedHTMLItemProvider.get() ]];
     [dataInteractionSimulator runFrom:CGPointMake(200, 300) to:CGPointMake(100, 300)];
 
index ba01117a5850e13ad3f04950fa9c02c2d8f8260e..17de7b917b4bf0856ba33c989f21e0ecda8f0666 100644 (file)
@@ -75,6 +75,7 @@ typedef NS_ENUM(NSInteger, DataInteractionPhase) {
 
 @property (nonatomic) BOOL allowsFocusToStartInputSession;
 @property (nonatomic) BOOL shouldEnsureUIApplication;
+@property (nonatomic) BOOL shouldAllowMoveOperation;
 @property (nonatomic) BlockPtr<BOOL(_WKActivatedElementInfo *)> showCustomActionSheetBlock;
 @property (nonatomic) BlockPtr<NSArray *(UIItemProvider *, NSArray *, NSDictionary *)> convertItemProvidersBlock;
 @property (nonatomic) BlockPtr<NSArray *(id <UIDropSession>)> overridePerformDropBlock;
index f5135cee636bbdaec2072ce3342d1a4558c0a177..48ab8e07a06f28ddff23ffbd511830bde1058083 100644 (file)
@@ -52,23 +52,25 @@ using namespace TestWebKitAPI;
     RetainPtr<UIWindow> _window;
 }
 @property (nonatomic) CGPoint mockLocationInWindow;
+@property (nonatomic) BOOL allowMove;
 @end
 
 @implementation MockDragDropSession
 
-- (instancetype)initWithItems:(NSArray <UIDragItem *>*)items location:(CGPoint)locationInWindow window:(UIWindow *)window
+- (instancetype)initWithItems:(NSArray <UIDragItem *>*)items location:(CGPoint)locationInWindow window:(UIWindow *)window allowMove:(BOOL)allowMove
 {
     if (self = [super init]) {
         _mockItems = items;
         _mockLocationInWindow = locationInWindow;
         _window = window;
+        _allowMove = allowMove;
     }
     return self;
 }
 
 - (BOOL)allowsMoveOperation
 {
-    return YES;
+    return _allowMove;
 }
 
 - (BOOL)isRestrictedToDraggingApplication
@@ -138,13 +140,13 @@ NSString * const DataInteractionStartEventName = @"dragstart";
 
 @implementation MockDataOperationSession
 
-- (instancetype)initWithProviders:(NSArray<UIItemProvider *> *)providers location:(CGPoint)locationInWindow window:(UIWindow *)window
+- (instancetype)initWithProviders:(NSArray<UIItemProvider *> *)providers location:(CGPoint)locationInWindow window:(UIWindow *)window allowMove:(BOOL)allowMove
 {
     auto items = adoptNS([[NSMutableArray alloc] init]);
     for (UIItemProvider *itemProvider in providers)
         [items addObject:[[[UIDragItem alloc] initWithItemProvider:itemProvider] autorelease]];
 
-    return [super initWithItems:items.get() location:locationInWindow window:window];
+    return [super initWithItems:items.get() location:locationInWindow window:window allowMove:allowMove];
 }
 
 - (UIDraggingSession *)session
@@ -208,9 +210,9 @@ NSString * const DataInteractionStartEventName = @"dragstart";
 
 @implementation MockDataInteractionSession
 
-- (instancetype)initWithWindow:(UIWindow *)window
+- (instancetype)initWithWindow:(UIWindow *)window allowMove:(BOOL)allowMove
 {
-    return [super initWithItems:@[ ] location:CGPointZero window:window];
+    return [super initWithItems:@[ ] location:CGPointZero window:window allowMove:allowMove];
 }
 
 - (NSUInteger)localOperationMask
@@ -263,6 +265,7 @@ static NSArray *dataInteractionEventNames()
     if (self = [super init]) {
         _webView = webView;
         _shouldEnsureUIApplication = NO;
+        _shouldAllowMoveOperation = YES;
         _isDoneWaitingForInputSession = true;
         [_webView setUIDelegate:self];
         [_webView _setInputDelegate:self];
@@ -331,11 +334,11 @@ static NSArray *dataInteractionEventNames()
     _endLocation = endLocation;
 
     if (self.externalItemProviders.count) {
-        _dataOperationSession = adoptNS([[MockDataOperationSession alloc] initWithProviders:self.externalItemProviders location:_startLocation window:[_webView window]]);
+        _dataOperationSession = adoptNS([[MockDataOperationSession alloc] initWithProviders:self.externalItemProviders location:_startLocation window:[_webView window] allowMove:self.shouldAllowMoveOperation]);
         _phase = DataInteractionBegan;
         [self _advanceProgress];
     } else {
-        _dataInteractionSession = adoptNS([[MockDataInteractionSession alloc] initWithWindow:[_webView window]]);
+        _dataInteractionSession = adoptNS([[MockDataInteractionSession alloc] initWithWindow:[_webView window] allowMove:self.shouldAllowMoveOperation]);
         [_dataInteractionSession setMockLocationInWindow:_startLocation];
         [_webView _simulatePrepareForDataInteractionSession:_dataInteractionSession.get() completion:^() {
             DataInteractionSimulator *weakSelf = strongSelf.get();
@@ -404,7 +407,7 @@ static NSArray *dataInteractionEventNames()
         for (UIDragItem *item in items)
             [itemProviders addObject:item.itemProvider];
 
-        _dataOperationSession = adoptNS([[MockDataOperationSession alloc] initWithProviders:itemProviders location:self._currentLocation window:[_webView window]]);
+        _dataOperationSession = adoptNS([[MockDataOperationSession alloc] initWithProviders:itemProviders location:self._currentLocation window:[_webView window] allowMove:self.shouldAllowMoveOperation]);
         [_dataInteractionSession setItems:items];
         _sourceItemProviders = itemProviders;
         if (self.showCustomActionSheetBlock) {
@@ -428,9 +431,11 @@ static NSArray *dataInteractionEventNames()
         [_webView _simulateDataInteractionEntered:_dataOperationSession.get()];
         _phase = DataInteractionEntered;
         break;
-    case DataInteractionEntered:
-        _shouldPerformOperation = [_webView _simulateDataInteractionUpdated:_dataOperationSession.get()];
+    case DataInteractionEntered: {
+        auto operation = static_cast<UIDropOperation>([_webView _simulateDataInteractionUpdated:_dataOperationSession.get()]);
+        _shouldPerformOperation = operation == UIDropOperationCopy || ([_dataOperationSession allowsMoveOperation] && operation != UIDropOperationCancel);
         break;
+    }
     default:
         break;
     }