[iOS DnD] Refactor drag and drop logic in WKContentView in preparation for supporting...
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 4 Sep 2017 23:36:20 +0000 (23:36 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 4 Sep 2017 23:36:20 +0000 (23:36 +0000)
https://bugs.webkit.org/show_bug.cgi?id=176264
<rdar://problem/31144674>

Reviewed by Darin Adler.

Source/WebCore:

Makes some small adjustments to WebItemProviderPasteboard. Rather than just -setItemProviders: on the
WebItemProviderPasteboard when initiating a drag, also stage the WebItemProviderRegistrationList that will be
used to generate an item provider. While it would be cleaner to avoid directly setting item providers so we
don't overwrite item providers on the WebItemProviderPasteboard when adding items to an existing drag session,
this isn't possible without breaking binary compability with older UIKit versions.

Importantly, WKContentView will now ignore any item providers that have been set on the
WebItemProviderPasteboard when initiating a drag or adding items to an existing drag session, and instead only
consider the staged registration list when generating item providers for dragging. This only has the drawback of
generating an unnecessary item provider, but otherwise maintains backwards compatibility while allowing us to
provide WebKit2 support for multiple items per drag session.

Tests: DataInteractionTests.DragEventClientCoordinatesBasic
       DataInteractionTests.DragEventClientCoordinatesWithScrollOffset
       DataInteractionTests.DragEventPageCoordinatesBasic
       DataInteractionTests.DragEventPageCoordinatesWithScrollOffset

* platform/ios/AbstractPasteboard.h:
* platform/ios/PlatformPasteboardIOS.mm:
(WebCore::registerItemToPasteboard):

Changed to only stage registration info on the item provider pasteboard, if possible. This has no effect on the
copy/paste codepath, since it uses a UIPasteboard.

* platform/ios/WebItemProviderPasteboard.h:
* platform/ios/WebItemProviderPasteboard.mm:

Rename _registrationInfoLists to _stagedRegistrationInfoList, and change it from an array of registration info
lists to a single registration info list. This could be updated in the future to be an array of registration
lists, but currently, it serves no purpose and makes coordination with DragItem info more difficult. This would
need to support multiple registration lists if we are to add a way to begin a drag containing multiple items in
vanilla web content, such as dragging multiple selections.

(-[WebItemProviderPasteboard init]):
(-[WebItemProviderPasteboard setItemProviders:]):
(-[WebItemProviderPasteboard stageRegistrationList:]):

Sets the staged item provider registration list.

(-[WebItemProviderPasteboard takeRegistrationList]):

Removes the staged item provider registration list from the WebItemProviderPasteboard and also returns it.

(-[WebItemProviderPasteboard registrationInfoAtIndex:]): Deleted.
(-[WebItemProviderPasteboard setRegistrationInfoLists:]): Deleted.

Source/WebKit:

Move DataInteractionState from WKContentViewInteraction.h to DragDropInteractionState.h, and also rename it to
DragDropInteractionState. Additionally, refactor drag and drop state in the UI process to capture metadata about
the dragged element in a separate DragSourceState struct. This patch also moves drag and drop state transition
logic that doesn't involve WKContentView internals out of WKContentView, and into the implementation of
DragDropInteractionState.

To support multiple drag items per session, we also introduce a simple mechanism to trace a UIDragItem back to
the DragSourceState used to generate it. When generating a DragSourceState, we assign it a unique identifier,
which we also set as the privateLocalContext of all UIDragItems generated when beginning the drag (this
includes drag items returned by an internal client that uses one of the SPI hooks to augment drag items when
starting a drag). This is subsequently used in the implementation of lift and cancellation preview delegate
methods to supply the appropriate drag preview for each UIDragItem.

Lastly, fix a bug wherein the pageX and pageY of mouse drag events are inconsistent with other synthetic mouse
events, such as synthetic clicks. For synthetic clicks, the PlatformMouseEvent is initialized with the same
position and globalPosition. Whether this is really intended is unclear (see http://webkit.org/b/173855), but
it's a trivial change for now to keep mouse events on iOS consistent by tweaking the behavior during drag and
drop. See Tools/ChangeLog for some more information.

* Platform/spi/ios/UIKitSPI.h:

Add -[UIDragItem privateLocalContext].

* UIProcess/ios/DragDropInteractionState.h: Added.
(WebKit::DragDropInteractionState::stagedDragSource const):
(WebKit::DragDropInteractionState::dropSessionDidExit):
(WebKit::DragDropInteractionState::dropSessionWillPerformDrop):
(WebKit::DragDropInteractionState::adjustedPositionForDragEnd const):
(WebKit::DragDropInteractionState::didBeginDragging const):
(WebKit::DragDropInteractionState::isPerformingDrop const):
(WebKit::DragDropInteractionState::dragSession const):
(WebKit::DragDropInteractionState::dropSession const):

Wrap private drag/drop state member variables behind const getters, and move drag and drop logic that involves
only the DragDropInteractionState into helper methods on DragDropInteractionState.

(WebKit::DragDropInteractionState::BlockPtr<void):
* UIProcess/ios/DragDropInteractionState.mm: Added.
(WebKit::dragItemMatchingIdentifier):
(WebKit::createTargetedDragPreview):
(WebKit::uiImageForImage):

Move drag preview creation logic here, from WKContentViewInteraction.mm.

(WebKit::shouldUseTextIndicatorToCreatePreviewForDragAction):
(WebKit::DragDropInteractionState::activeDragSourceForItem const):
(WebKit::DragDropInteractionState::anyActiveDragSourceIs const):
(WebKit::DragDropInteractionState::prepareForDragSession):
(WebKit::DragDropInteractionState::dragSessionWillBegin):
(WebKit::DragDropInteractionState::previewForDragItem const):
(WebKit::DragDropInteractionState::dragSessionWillDelaySetDownAnimation):
(WebKit::DragDropInteractionState::dropSessionDidEnterOrUpdate):
(WebKit::DragDropInteractionState::stageDragItem):
(WebKit::DragDropInteractionState::hasStagedDragSource const):
(WebKit::DragDropInteractionState::clearStagedDragSource):
(WebKit::DragDropInteractionState::dragAndDropSessionsDidEnd):
(WebKit::DragDropInteractionState::updatePreviewsForActiveDragSources):
* UIProcess/ios/WKContentViewInteraction.h:

Move drag-and-drop-related state tied to the WKContentView here, from DataInteractionState (for instance, the
UIView for the drop caret, the WKContentView snapshot when dropping, and a flag use to keep track of hiding the
callout bar when dragging a text selection).

(): Deleted.
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView _startAssistingNode:userIsInteracting:blurPreviousNode:userObject:]):
(-[WKContentView actionSheetAssistant:showCustomSheetForElement:]):
(-[WKContentView _didChangeDragInteractionPolicy]):
(-[WKContentView setupDataInteractionDelegates]):
(-[WKContentView teardownDataInteractionDelegates]):
(-[WKContentView _startDrag:item:]):
(-[WKContentView _didHandleStartDataInteractionRequest:]):
(-[WKContentView computeClientAndGlobalPointsForDropSession:outClientPoint:outGlobalPoint:]):
(-[WKContentView cleanUpDragSourceSessionState]):
(-[WKContentView _didConcludeEditDataInteraction:]):
(-[WKContentView _didPerformDataInteractionControllerOperation:]):
(-[WKContentView _didChangeDataInteractionCaretRect:currentRect:]):
(-[WKContentView currentDragOrDropSession]):
(-[WKContentView _restoreCalloutBarIfNeeded]):
(-[WKContentView _dragInteraction:prepareForSession:completion:]):
(-[WKContentView dragInteraction:itemsForBeginningSession:]):
(-[WKContentView dragInteraction:previewForLiftingItem:session:]):
(-[WKContentView dragInteraction:willAnimateLiftWithAnimator:session:]):
(-[WKContentView dragInteraction:sessionWillBegin:]):
(-[WKContentView dragInteraction:session:didEndWithOperation:]):
(-[WKContentView dragInteraction:previewForCancellingItem:withDefault:]):
(-[WKContentView _dragInteraction:item:shouldDelaySetDownAnimationWithCompletion:]):
(-[WKContentView dragInteraction:item:willAnimateCancelWithAnimator:]):
(-[WKContentView dropInteraction:sessionDidEnter:]):
(-[WKContentView dropInteraction:sessionDidUpdate:]):
(-[WKContentView dropInteraction:sessionDidExit:]):
(-[WKContentView dropInteraction:performDrop:]):
(-[WKContentView dropInteraction:sessionDidEnd:]):

Pull out logic that mutates drag and drop state into DragDropInteractionState. Adjust places that previously
accessed DataInteractionState's members directly with corresponding getters in DragDropInteractionState.

(-[WKContentView _simulateDataInteractionEntered:]):
(-[WKContentView _simulateDataInteractionUpdated:]):
(-[WKContentView _simulateDataInteractionEnded:]):
(-[WKContentView _simulateDataInteractionPerformOperation:]):
(-[WKContentView _simulateDataInteractionSessionDidEnd:]):
(-[WKContentView _simulateWillBeginDataInteractionWithSession:]):
(-[WKContentView _simulatedItemsForSession:]):
(-[WKContentView _simulatePrepareForDataInteractionSession:completion:]):

Rename _dataInteraction => _dragInteraction and _dataOperation => _dropInteraction.

(uiImageForImage): Deleted.
(shouldUseTextIndicatorToCreatePreviewForDragAction): Deleted.
(-[WKContentView dragPreviewForImage:frameInRootViewCoordinates:clippingRectsInFrameCoordinates:backgroundColor:]): Deleted.
(-[WKContentView dragPreviewForCurrentDataInteractionState]): Deleted.
(-[WKContentView _transitionDragPreviewToImageIfNecessary:]): Deleted.
* WebKit.xcodeproj/project.pbxproj:

Tools:

Adds two new iOS drag and drop tests to check that the clientX and clientY attributes of mouse events propagated
to the page during drop are correct. Each test drags from an image element and drops into three custom-drop-
handling elements; `dragenter`, `dragover`, and `drop` event listeners on the body then use the clientX and
clientY event attributes to hit-test for drop target elements. The first test is suffixed with "-Basic"; the
second test, suffixed with "-WithScrollOffset", makes the document scrollable to check that clientY is correct
when scrolled.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/drop-targets.html: Added.
* TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
(TestWebKitAPI::testDragAndDropOntoTargetElements):
(TestWebKitAPI::TEST):
* TestWebKitAPI/cocoa/TestWKWebView.mm:
(-[TestWKWebView stringByEvaluatingJavaScript:]):

Log a warning message when an API test fails due to JavaScript evaluation in TestWKWebView.

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

17 files changed:
Source/WebCore/ChangeLog
Source/WebCore/platform/ios/AbstractPasteboard.h
Source/WebCore/platform/ios/PlatformPasteboardIOS.mm
Source/WebCore/platform/ios/WebItemProviderPasteboard.h
Source/WebCore/platform/ios/WebItemProviderPasteboard.mm
Source/WebKit/ChangeLog
Source/WebKit/Platform/spi/ios/UIKitSPI.h
Source/WebKit/UIProcess/ios/DragDropInteractionState.h [new file with mode: 0644]
Source/WebKit/UIProcess/ios/DragDropInteractionState.mm [new file with mode: 0644]
Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/drop-targets.html [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/ios/DataInteractionTests.mm
Tools/TestWebKitAPI/cocoa/TestWKWebView.mm

index e85b34f935246fb4ee75706355063182b38c5658..a570b2c0ca374d24fef92c6f7af49f17b3acb3bf 100644 (file)
@@ -1,3 +1,57 @@
+2017-09-04  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS DnD] Refactor drag and drop logic in WKContentView in preparation for supporting multiple drag items in a drag session
+        https://bugs.webkit.org/show_bug.cgi?id=176264
+        <rdar://problem/31144674>
+
+        Reviewed by Darin Adler.
+
+        Makes some small adjustments to WebItemProviderPasteboard. Rather than just -setItemProviders: on the
+        WebItemProviderPasteboard when initiating a drag, also stage the WebItemProviderRegistrationList that will be
+        used to generate an item provider. While it would be cleaner to avoid directly setting item providers so we
+        don't overwrite item providers on the WebItemProviderPasteboard when adding items to an existing drag session,
+        this isn't possible without breaking binary compability with older UIKit versions.
+
+        Importantly, WKContentView will now ignore any item providers that have been set on the
+        WebItemProviderPasteboard when initiating a drag or adding items to an existing drag session, and instead only
+        consider the staged registration list when generating item providers for dragging. This only has the drawback of
+        generating an unnecessary item provider, but otherwise maintains backwards compatibility while allowing us to
+        provide WebKit2 support for multiple items per drag session.
+
+        Tests: DataInteractionTests.DragEventClientCoordinatesBasic
+               DataInteractionTests.DragEventClientCoordinatesWithScrollOffset
+               DataInteractionTests.DragEventPageCoordinatesBasic
+               DataInteractionTests.DragEventPageCoordinatesWithScrollOffset
+
+        * platform/ios/AbstractPasteboard.h:
+        * platform/ios/PlatformPasteboardIOS.mm:
+        (WebCore::registerItemToPasteboard):
+
+        Changed to only stage registration info on the item provider pasteboard, if possible. This has no effect on the
+        copy/paste codepath, since it uses a UIPasteboard.
+
+        * platform/ios/WebItemProviderPasteboard.h:
+        * platform/ios/WebItemProviderPasteboard.mm:
+
+        Rename _registrationInfoLists to _stagedRegistrationInfoList, and change it from an array of registration info
+        lists to a single registration info list. This could be updated in the future to be an array of registration
+        lists, but currently, it serves no purpose and makes coordination with DragItem info more difficult. This would
+        need to support multiple registration lists if we are to add a way to begin a drag containing multiple items in
+        vanilla web content, such as dragging multiple selections.
+
+        (-[WebItemProviderPasteboard init]):
+        (-[WebItemProviderPasteboard setItemProviders:]):
+        (-[WebItemProviderPasteboard stageRegistrationList:]):
+
+        Sets the staged item provider registration list.
+
+        (-[WebItemProviderPasteboard takeRegistrationList]):
+
+        Removes the staged item provider registration list from the WebItemProviderPasteboard and also returns it.
+
+        (-[WebItemProviderPasteboard registrationInfoAtIndex:]): Deleted.
+        (-[WebItemProviderPasteboard setRegistrationInfoLists:]): Deleted.
+
 2017-09-04  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r221494 and r221500.
index 9836cb3fdfc434c6910dc82ded233b5dbb925fd2..9a88e78e49e950f12ef13270df2413f74144110d 100644 (file)
@@ -49,7 +49,8 @@ NS_ASSUME_NONNULL_BEGIN
 
 @optional
 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000
-- (void)setRegistrationInfoLists:(NSArray <WebItemProviderRegistrationInfoList *> *)info;
+- (void)stageRegistrationList:(nullable WebItemProviderRegistrationInfoList *)info;
+- (nullable WebItemProviderRegistrationInfoList *)takeRegistrationList;
 #endif
 - (void)setItems:(NSArray<NSDictionary *> *)items;
 - (NSArray<NSString *> *)pasteboardTypesByFidelityForItemAtIndex:(NSUInteger)index;
index 25bfb7a787b47119a3927c2ea1c676210eb45c1d..91f6c9a91f472cdd0b578d1e51cac2c03f4b763a 100644 (file)
@@ -179,15 +179,13 @@ static NSString *webIOSPastePboardType = @"iOS rich content paste pasteboard typ
 
 static void registerItemToPasteboard(WebItemProviderRegistrationInfoList *representationsToRegister, id <AbstractPasteboard> pasteboard)
 {
-    UIItemProvider *itemProvider = [representationsToRegister itemProvider];
-    if (!itemProvider) {
+    if (UIItemProvider *itemProvider = representationsToRegister.itemProvider)
+        [pasteboard setItemProviders:@[ itemProvider ]];
+    else
         [pasteboard setItemProviders:@[ ]];
-        return;
-    }
 
-    [pasteboard setItemProviders:@[ itemProvider ]];
-    if ([pasteboard respondsToSelector:@selector(setRegistrationInfoLists:)])
-        [pasteboard setRegistrationInfoLists:@[ representationsToRegister ]];
+    if ([pasteboard respondsToSelector:@selector(stageRegistrationList:)])
+        [pasteboard stageRegistrationList:representationsToRegister];
 }
 
 #else
index 2508fd1f1c4bb729a8cf3341dacade84afaa4ef8..b92a2740e59a52e3a4062f1e55b13d766e790994 100644 (file)
@@ -76,10 +76,6 @@ WEBCORE_EXPORT @interface WebItemProviderPasteboard : NSObject<AbstractPasteboar
 
 + (instancetype)sharedInstance;
 
-// Registration info lists are only available upon starting data interaction.
-- (WebItemProviderRegistrationInfoList *)registrationInfoAtIndex:(NSUInteger)index;
-- (UIItemProvider *)itemProviderAtIndex:(NSUInteger)index;
-
 @property (copy, nonatomic, nullable) NSArray<__kindof NSItemProvider *> *itemProviders;
 @property (readonly, nonatomic) NSInteger numberOfItems;
 @property (readonly, nonatomic) NSInteger changeCount;
index d86269a71b6c662514ed32dbc09f1164e2cf2b2d..4bbb61cc47feb55fa4deeca8bcbaf6000a68975d 100644 (file)
@@ -207,7 +207,7 @@ typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
     RetainPtr<NSArray> _cachedTypeIdentifiers;
     RetainPtr<NSArray> _typeToFileURLMaps;
     RetainPtr<NSArray> _supportedTypeIdentifiers;
-    RetainPtr<NSArray> _registrationInfoLists;
+    RetainPtr<WebItemProviderRegistrationInfoList> _stagedRegistrationInfoList;
 }
 
 + (instancetype)sharedInstance
@@ -228,7 +228,7 @@ typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
         _pendingOperationCount = 0;
         _typeToFileURLMaps = adoptNS([[NSArray alloc] init]);
         _supportedTypeIdentifiers = nil;
-        _registrationInfoLists = nil;
+        _stagedRegistrationInfoList = nil;
     }
     return self;
 }
@@ -277,7 +277,7 @@ typedef NSDictionary<NSString *, NSURL *> TypeToFileURLMap;
     _itemProviders = itemProviders;
     _changeCount++;
     _cachedTypeIdentifiers = nil;
-    _registrationInfoLists = nil;
+    _stagedRegistrationInfoList = nil;
 
     NSMutableArray *typeToFileURLMaps = [NSMutableArray arrayWithCapacity:itemProviders.count];
     [itemProviders enumerateObjectsUsingBlock:[typeToFileURLMaps] (UIItemProvider *, NSUInteger, BOOL *) {
@@ -515,11 +515,6 @@ static NSURL *temporaryFileURLForDataInteractionContent(NSURL *url, NSString *su
     dispatch_group_notify(fileLoadingGroup.get(), dispatch_get_main_queue(), itemLoadCompletion);
 }
 
-- (WebItemProviderRegistrationInfoList *)registrationInfoAtIndex:(NSUInteger)index
-{
-    return index < [_registrationInfoLists count] ? [_registrationInfoLists objectAtIndex:index] : nil;
-}
-
 - (UIItemProvider *)itemProviderAtIndex:(NSUInteger)index
 {
     return index < [_itemProviders count] ? [_itemProviders objectAtIndex:index] : nil;
@@ -545,9 +540,16 @@ static NSURL *temporaryFileURLForDataInteractionContent(NSURL *url, NSString *su
     [_itemProviders enumerateObjectsUsingBlock:block];
 }
 
-- (void)setRegistrationInfoLists:(NSArray <WebItemProviderRegistrationInfoList *> *)infoLists
+- (void)stageRegistrationList:(nullable WebItemProviderRegistrationInfoList *)info
+{
+    _stagedRegistrationInfoList = info.numberOfItems ? info : nil;
+}
+
+- (WebItemProviderRegistrationInfoList *)takeRegistrationList
 {
-    _registrationInfoLists = infoLists;
+    auto stagedRegistrationInfoList = _stagedRegistrationInfoList;
+    _stagedRegistrationInfoList = nil;
+    return stagedRegistrationInfoList.autorelease();
 }
 
 @end
index da8d8ddc25e3594753db12aa56928fff96a69223..8fa56a868ab4fbeeec3431fadb5be87032e2831b 100644 (file)
@@ -1,3 +1,126 @@
+2017-09-04  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS DnD] Refactor drag and drop logic in WKContentView in preparation for supporting multiple drag items in a drag session
+        https://bugs.webkit.org/show_bug.cgi?id=176264
+        <rdar://problem/31144674>
+
+        Reviewed by Darin Adler.
+
+        Move DataInteractionState from WKContentViewInteraction.h to DragDropInteractionState.h, and also rename it to
+        DragDropInteractionState. Additionally, refactor drag and drop state in the UI process to capture metadata about
+        the dragged element in a separate DragSourceState struct. This patch also moves drag and drop state transition
+        logic that doesn't involve WKContentView internals out of WKContentView, and into the implementation of
+        DragDropInteractionState.
+
+        To support multiple drag items per session, we also introduce a simple mechanism to trace a UIDragItem back to
+        the DragSourceState used to generate it. When generating a DragSourceState, we assign it a unique identifier,
+        which we also set as the privateLocalContext of all UIDragItems generated when beginning the drag (this
+        includes drag items returned by an internal client that uses one of the SPI hooks to augment drag items when
+        starting a drag). This is subsequently used in the implementation of lift and cancellation preview delegate
+        methods to supply the appropriate drag preview for each UIDragItem.
+
+        Lastly, fix a bug wherein the pageX and pageY of mouse drag events are inconsistent with other synthetic mouse
+        events, such as synthetic clicks. For synthetic clicks, the PlatformMouseEvent is initialized with the same
+        position and globalPosition. Whether this is really intended is unclear (see http://webkit.org/b/173855), but
+        it's a trivial change for now to keep mouse events on iOS consistent by tweaking the behavior during drag and
+        drop. See Tools/ChangeLog for some more information.
+
+        * Platform/spi/ios/UIKitSPI.h:
+
+        Add -[UIDragItem privateLocalContext].
+
+        * UIProcess/ios/DragDropInteractionState.h: Added.
+        (WebKit::DragDropInteractionState::stagedDragSource const):
+        (WebKit::DragDropInteractionState::dropSessionDidExit):
+        (WebKit::DragDropInteractionState::dropSessionWillPerformDrop):
+        (WebKit::DragDropInteractionState::adjustedPositionForDragEnd const):
+        (WebKit::DragDropInteractionState::didBeginDragging const):
+        (WebKit::DragDropInteractionState::isPerformingDrop const):
+        (WebKit::DragDropInteractionState::dragSession const):
+        (WebKit::DragDropInteractionState::dropSession const):
+
+        Wrap private drag/drop state member variables behind const getters, and move drag and drop logic that involves
+        only the DragDropInteractionState into helper methods on DragDropInteractionState.
+
+        (WebKit::DragDropInteractionState::BlockPtr<void):
+        * UIProcess/ios/DragDropInteractionState.mm: Added.
+        (WebKit::dragItemMatchingIdentifier):
+        (WebKit::createTargetedDragPreview):
+        (WebKit::uiImageForImage):
+
+        Move drag preview creation logic here, from WKContentViewInteraction.mm.
+
+        (WebKit::shouldUseTextIndicatorToCreatePreviewForDragAction):
+        (WebKit::DragDropInteractionState::activeDragSourceForItem const):
+        (WebKit::DragDropInteractionState::anyActiveDragSourceIs const):
+        (WebKit::DragDropInteractionState::prepareForDragSession):
+        (WebKit::DragDropInteractionState::dragSessionWillBegin):
+        (WebKit::DragDropInteractionState::previewForDragItem const):
+        (WebKit::DragDropInteractionState::dragSessionWillDelaySetDownAnimation):
+        (WebKit::DragDropInteractionState::dropSessionDidEnterOrUpdate):
+        (WebKit::DragDropInteractionState::stageDragItem):
+        (WebKit::DragDropInteractionState::hasStagedDragSource const):
+        (WebKit::DragDropInteractionState::clearStagedDragSource):
+        (WebKit::DragDropInteractionState::dragAndDropSessionsDidEnd):
+        (WebKit::DragDropInteractionState::updatePreviewsForActiveDragSources):
+        * UIProcess/ios/WKContentViewInteraction.h:
+
+        Move drag-and-drop-related state tied to the WKContentView here, from DataInteractionState (for instance, the
+        UIView for the drop caret, the WKContentView snapshot when dropping, and a flag use to keep track of hiding the
+        callout bar when dragging a text selection).
+
+        (): Deleted.
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView _startAssistingNode:userIsInteracting:blurPreviousNode:userObject:]):
+        (-[WKContentView actionSheetAssistant:showCustomSheetForElement:]):
+        (-[WKContentView _didChangeDragInteractionPolicy]):
+        (-[WKContentView setupDataInteractionDelegates]):
+        (-[WKContentView teardownDataInteractionDelegates]):
+        (-[WKContentView _startDrag:item:]):
+        (-[WKContentView _didHandleStartDataInteractionRequest:]):
+        (-[WKContentView computeClientAndGlobalPointsForDropSession:outClientPoint:outGlobalPoint:]):
+        (-[WKContentView cleanUpDragSourceSessionState]):
+        (-[WKContentView _didConcludeEditDataInteraction:]):
+        (-[WKContentView _didPerformDataInteractionControllerOperation:]):
+        (-[WKContentView _didChangeDataInteractionCaretRect:currentRect:]):
+        (-[WKContentView currentDragOrDropSession]):
+        (-[WKContentView _restoreCalloutBarIfNeeded]):
+        (-[WKContentView _dragInteraction:prepareForSession:completion:]):
+        (-[WKContentView dragInteraction:itemsForBeginningSession:]):
+        (-[WKContentView dragInteraction:previewForLiftingItem:session:]):
+        (-[WKContentView dragInteraction:willAnimateLiftWithAnimator:session:]):
+        (-[WKContentView dragInteraction:sessionWillBegin:]):
+        (-[WKContentView dragInteraction:session:didEndWithOperation:]):
+        (-[WKContentView dragInteraction:previewForCancellingItem:withDefault:]):
+        (-[WKContentView _dragInteraction:item:shouldDelaySetDownAnimationWithCompletion:]):
+        (-[WKContentView dragInteraction:item:willAnimateCancelWithAnimator:]):
+        (-[WKContentView dropInteraction:sessionDidEnter:]):
+        (-[WKContentView dropInteraction:sessionDidUpdate:]):
+        (-[WKContentView dropInteraction:sessionDidExit:]):
+        (-[WKContentView dropInteraction:performDrop:]):
+        (-[WKContentView dropInteraction:sessionDidEnd:]):
+
+        Pull out logic that mutates drag and drop state into DragDropInteractionState. Adjust places that previously
+        accessed DataInteractionState's members directly with corresponding getters in DragDropInteractionState.
+
+        (-[WKContentView _simulateDataInteractionEntered:]):
+        (-[WKContentView _simulateDataInteractionUpdated:]):
+        (-[WKContentView _simulateDataInteractionEnded:]):
+        (-[WKContentView _simulateDataInteractionPerformOperation:]):
+        (-[WKContentView _simulateDataInteractionSessionDidEnd:]):
+        (-[WKContentView _simulateWillBeginDataInteractionWithSession:]):
+        (-[WKContentView _simulatedItemsForSession:]):
+        (-[WKContentView _simulatePrepareForDataInteractionSession:completion:]):
+
+        Rename _dataInteraction => _dragInteraction and _dataOperation => _dropInteraction.
+
+        (uiImageForImage): Deleted.
+        (shouldUseTextIndicatorToCreatePreviewForDragAction): Deleted.
+        (-[WKContentView dragPreviewForImage:frameInRootViewCoordinates:clippingRectsInFrameCoordinates:backgroundColor:]): Deleted.
+        (-[WKContentView dragPreviewForCurrentDataInteractionState]): Deleted.
+        (-[WKContentView _transitionDragPreviewToImageIfNecessary:]): Deleted.
+        * WebKit.xcodeproj/project.pbxproj:
+
 2017-09-03  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         [Threaded Compositor] Deadlock in ThreadedDisplayRefreshMonitor
index ef49530e5990e6a1c46e5f54e6bebf00c92b5998..c91bb9237ec6ec6f0dece75d7708697d86822c53 100644 (file)
@@ -90,6 +90,7 @@
 #if ENABLE(DRAG_SUPPORT)
 #import <UIKit/UIDragInteraction.h>
 #import <UIKit/UIDragInteraction_Private.h>
+#import <UIKit/UIDragItem_Private.h>
 #import <UIKit/UIDragPreviewParameters.h>
 #import <UIKit/UIDragPreview_Private.h>
 #import <UIKit/UIDragSession.h>
@@ -899,6 +900,10 @@ typedef NS_OPTIONS(NSUInteger, UIDragOperation)
 @property (nonatomic, assign, getter=_liftDelay, setter=_setLiftDelay:) NSTimeInterval liftDelay;
 @end
 
+@interface UIDragItem ()
+@property (nonatomic, strong, nullable, setter=_setPrivateLocalContext:, getter=_privateLocalContext) id privateLocalContext;
+@end
+
 @protocol UITextInput;
 @interface _UITextDragCaretView : UIView
 - (instancetype)initWithTextInputView:(UIView<UITextInput> *)textInputView;
diff --git a/Source/WebKit/UIProcess/ios/DragDropInteractionState.h b/Source/WebKit/UIProcess/ios/DragDropInteractionState.h
new file mode 100644 (file)
index 0000000..a3d8aeb
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(DRAG_SUPPORT) && PLATFORM(IOS)
+
+#import "UIKitSPI.h"
+#import <WebCore/DragActions.h>
+#import <WebCore/DragData.h>
+#import <WebCore/TextIndicator.h>
+#import <WebCore/URL.h>
+#import <WebCore/WebItemProviderPasteboard.h>
+#import <wtf/BlockPtr.h>
+#import <wtf/RetainPtr.h>
+#import <wtf/Vector.h>
+
+namespace WebCore {
+struct DragItem;
+}
+
+namespace WebKit {
+
+struct DragSourceState {
+    WebCore::DragSourceAction action { WebCore::DragSourceActionNone };
+    CGPoint adjustedOrigin { CGPointZero };
+    CGRect elementBounds { CGRectZero };
+    RetainPtr<UIImage> image;
+    std::optional<WebCore::TextIndicatorData> indicatorData;
+    String linkTitle;
+    WebCore::URL linkURL;
+    bool possiblyNeedsDragPreviewUpdate { true };
+
+    NSInteger itemIdentifier { 0 };
+};
+
+class DragDropInteractionState {
+public:
+    bool anyActiveDragSourceIs(WebCore::DragSourceAction) const;
+
+    // These helper methods are unique to UIDragInteraction.
+    void prepareForDragSession(id <UIDragSession>, dispatch_block_t completionHandler);
+    void dragSessionWillBegin();
+    void stageDragItem(const WebCore::DragItem&, UIImage *);
+    bool hasStagedDragSource() const;
+    const DragSourceState& stagedDragSource() const { return m_stagedDragSource.value(); }
+    enum class DidBecomeActive { No, Yes };
+    void clearStagedDragSource(DidBecomeActive = DidBecomeActive::No);
+    UITargetedDragPreview *previewForDragItem(UIDragItem *, UIView *contentView, UIView *previewContainer) const;
+    void dragSessionWillDelaySetDownAnimation(dispatch_block_t completion);
+
+    // These helper methods are unique to UIDropInteraction.
+    void dropSessionDidEnterOrUpdate(id <UIDropSession>, const WebCore::DragData&);
+    void dropSessionDidExit() { m_dropSession = nil; }
+    void dropSessionWillPerformDrop() { m_isPerformingDrop = true; }
+
+    // This is invoked when both drag and drop interactions are no longer active.
+    void dragAndDropSessionsDidEnd();
+
+    CGPoint adjustedPositionForDragEnd() const { return m_adjustedPositionForDragEnd; }
+    bool didBeginDragging() const { return m_didBeginDragging; }
+    bool isPerformingDrop() const { return m_isPerformingDrop; }
+    id<UIDragSession> dragSession() const { return m_dragSession.get(); }
+    id<UIDropSession> dropSession() const { return m_dropSession.get(); }
+    BlockPtr<void()> takeDragStartCompletionBlock() { return WTFMove(m_dragStartCompletionBlock); }
+    BlockPtr<void()> takeDragCancelSetDownBlock() { return WTFMove(m_dragCancelSetDownBlock); }
+
+private:
+    void updatePreviewsForActiveDragSources();
+    std::optional<DragSourceState> activeDragSourceForItem(UIDragItem *) const;
+
+    CGPoint m_lastGlobalPosition { CGPointZero };
+    CGPoint m_adjustedPositionForDragEnd { CGPointZero };
+    bool m_didBeginDragging { false };
+    bool m_isPerformingDrop { false };
+    RetainPtr<id <UIDragSession>> m_dragSession;
+    RetainPtr<id <UIDropSession>> m_dropSession;
+    BlockPtr<void()> m_dragStartCompletionBlock;
+    BlockPtr<void()> m_dragCancelSetDownBlock;
+
+    std::optional<DragSourceState> m_stagedDragSource;
+    Vector<DragSourceState> m_activeDragSources;
+};
+
+} // namespace WebKit
+
+#endif // ENABLE(DRAG_SUPPORT) && PLATFORM(IOS)
diff --git a/Source/WebKit/UIProcess/ios/DragDropInteractionState.mm b/Source/WebKit/UIProcess/ios/DragDropInteractionState.mm
new file mode 100644 (file)
index 0000000..73ff07b
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "DragDropInteractionState.h"
+
+#if ENABLE(DRAG_SUPPORT) && PLATFORM(IOS)
+
+#import <WebCore/DragItem.h>
+#import <WebCore/Image.h>
+
+using namespace WebCore;
+using namespace WebKit;
+
+namespace WebKit {
+
+static UIDragItem *dragItemMatchingIdentifier(id <UIDragSession> session, NSInteger identifier)
+{
+    for (UIDragItem *item in session.items) {
+        id context = item.privateLocalContext;
+        if ([context isKindOfClass:[NSNumber class]] && [context integerValue] == identifier)
+            return item;
+    }
+    return nil;
+}
+
+static UITargetedDragPreview *createTargetedDragPreview(UIImage *image, UIView *rootView, UIView *previewContainer, const FloatRect& frameInRootViewCoordinates, const Vector<FloatRect>& clippingRectsInFrameCoordinates, UIColor *backgroundColor)
+{
+    if (frameInRootViewCoordinates.isEmpty() || !image)
+        return nullptr;
+
+    NSMutableArray *clippingRectValuesInFrameCoordinates = [NSMutableArray arrayWithCapacity:clippingRectsInFrameCoordinates.size()];
+
+    FloatRect frameInContainerCoordinates = [rootView convertRect:frameInRootViewCoordinates toView:previewContainer];
+    if (frameInContainerCoordinates.isEmpty())
+        return nullptr;
+
+    float widthScalingRatio = frameInContainerCoordinates.width() / frameInRootViewCoordinates.width();
+    float heightScalingRatio = frameInContainerCoordinates.height() / frameInRootViewCoordinates.height();
+    for (auto rect : clippingRectsInFrameCoordinates) {
+        rect.scale(widthScalingRatio, heightScalingRatio);
+        [clippingRectValuesInFrameCoordinates addObject:[NSValue valueWithCGRect:rect]];
+    }
+
+    auto imageView = adoptNS([[UIImageView alloc] initWithImage:image]);
+    [imageView setFrame:frameInContainerCoordinates];
+
+    RetainPtr<UIDragPreviewParameters> parameters;
+    if (clippingRectValuesInFrameCoordinates.count)
+        parameters = adoptNS([[UIDragPreviewParameters alloc] initWithTextLineRects:clippingRectValuesInFrameCoordinates]);
+    else
+        parameters = adoptNS([[UIDragPreviewParameters alloc] init]);
+
+    if (backgroundColor)
+        [parameters setBackgroundColor:backgroundColor];
+
+    CGPoint centerInContainerCoordinates = { CGRectGetMidX(frameInContainerCoordinates), CGRectGetMidY(frameInContainerCoordinates) };
+    auto target = adoptNS([[UIDragPreviewTarget alloc] initWithContainer:previewContainer center:centerInContainerCoordinates]);
+    auto dragPreview = adoptNS([[UITargetedDragPreview alloc] initWithView:imageView.get() parameters:parameters.get() target:target.get()]);
+    return dragPreview.autorelease();
+}
+
+static RetainPtr<UIImage> uiImageForImage(Image* image)
+{
+    if (!image)
+        return nullptr;
+
+    auto cgImage = image->nativeImage();
+    if (!cgImage)
+        return nullptr;
+
+    return adoptNS([[UIImage alloc] initWithCGImage:cgImage.get()]);
+}
+
+static bool shouldUseTextIndicatorToCreatePreviewForDragAction(DragSourceAction action)
+{
+    if (action & (DragSourceActionLink | DragSourceActionSelection))
+        return true;
+
+#if ENABLE(ATTACHMENT_ELEMENT)
+    if (action & DragSourceActionAttachment)
+        return true;
+#endif
+
+    return false;
+}
+
+std::optional<DragSourceState> DragDropInteractionState::activeDragSourceForItem(UIDragItem *item) const
+{
+    if (![item.privateLocalContext isKindOfClass:[NSNumber class]])
+        return std::nullopt;
+
+    auto identifier = [(NSNumber *)item.privateLocalContext integerValue];
+    for (auto& source : m_activeDragSources) {
+        if (source.itemIdentifier == identifier)
+            return source;
+    }
+    return std::nullopt;
+}
+
+bool DragDropInteractionState::anyActiveDragSourceIs(WebCore::DragSourceAction action) const
+{
+    for (auto& source : m_activeDragSources) {
+        if (source.action & action)
+            return true;
+    }
+    return false;
+}
+
+void DragDropInteractionState::prepareForDragSession(id <UIDragSession> session, dispatch_block_t completionHandler)
+{
+    m_dragSession = session;
+    m_dragStartCompletionBlock = completionHandler;
+}
+
+void DragDropInteractionState::dragSessionWillBegin()
+{
+    m_didBeginDragging = true;
+    updatePreviewsForActiveDragSources();
+}
+
+UITargetedDragPreview *DragDropInteractionState::previewForDragItem(UIDragItem *item, UIView *contentView, UIView *previewContainer) const
+{
+    auto foundSource = activeDragSourceForItem(item);
+    if (!foundSource)
+        return nil;
+
+    auto& source = foundSource.value();
+    if ((source.action & DragSourceActionImage) && source.image) {
+        Vector<FloatRect> emptyClippingRects;
+        return createTargetedDragPreview(source.image.get(), contentView, previewContainer, source.elementBounds, emptyClippingRects, nil);
+    }
+
+    if (shouldUseTextIndicatorToCreatePreviewForDragAction(source.action) && source.indicatorData) {
+        auto indicator = source.indicatorData.value();
+        auto textIndicatorImage = uiImageForImage(indicator.contentImage.get());
+        return createTargetedDragPreview(textIndicatorImage.get(), contentView, previewContainer, indicator.textBoundingRectInRootViewCoordinates, indicator.textRectsInBoundingRectCoordinates, [UIColor colorWithCGColor:cachedCGColor(indicator.estimatedBackgroundColor)]);
+    }
+
+    return nil;
+}
+
+void DragDropInteractionState::dragSessionWillDelaySetDownAnimation(dispatch_block_t completion)
+{
+    m_dragCancelSetDownBlock = completion;
+}
+
+void DragDropInteractionState::dropSessionDidEnterOrUpdate(id <UIDropSession> session, const DragData& dragData)
+{
+    m_dropSession = session;
+    m_lastGlobalPosition = dragData.globalPosition();
+}
+
+void DragDropInteractionState::stageDragItem(const DragItem& item, UIImage *dragImage)
+{
+    static NSInteger currentDragSourceItemIdentifier = 0;
+
+    m_adjustedPositionForDragEnd = item.eventPositionInContentCoordinates;
+    m_stagedDragSource = {{
+        static_cast<DragSourceAction>(item.sourceAction),
+        item.eventPositionInContentCoordinates,
+        item.elementBounds,
+        dragImage,
+        item.image.indicatorData(),
+        item.title.isEmpty() ? nil : (NSString *)item.title,
+        item.url.isEmpty() ? nil : (NSURL *)item.url,
+        true, // We assume here that drag previews need to be updated until proven otherwise in updatePreviewsForActiveDragSources().
+        ++currentDragSourceItemIdentifier
+    }};
+}
+
+bool DragDropInteractionState::hasStagedDragSource() const
+{
+    return m_stagedDragSource && stagedDragSource().action != WebCore::DragSourceActionNone;
+}
+
+void DragDropInteractionState::clearStagedDragSource(DidBecomeActive didBecomeActive)
+{
+    if (didBecomeActive == DidBecomeActive::Yes)
+        m_activeDragSources.append(stagedDragSource());
+    m_stagedDragSource = std::nullopt;
+}
+
+void DragDropInteractionState::dragAndDropSessionsDidEnd()
+{
+    // If any of UIKit's completion blocks are still in-flight when the drag interaction ends, we need to ensure that they are still invoked
+    // to prevent UIKit from getting into an inconsistent state.
+    if (auto completionBlock = takeDragCancelSetDownBlock())
+        completionBlock();
+
+    if (auto completionBlock = takeDragStartCompletionBlock())
+        completionBlock();
+}
+
+void DragDropInteractionState::updatePreviewsForActiveDragSources()
+{
+    for (auto& source : m_activeDragSources) {
+        if (!source.possiblyNeedsDragPreviewUpdate)
+            continue;
+
+        if (source.action & DragSourceActionImage || !(source.action & DragSourceActionLink)) {
+            // Currently, non-image links are the only type of source for which we need to update
+            // drag preview providers after the initial lift. All other dragged content should maintain
+            // the same targeted drag preview used during the lift animation.
+            continue;
+        }
+
+        UIDragItem *dragItem = dragItemMatchingIdentifier(m_dragSession.get(), source.itemIdentifier);
+        if (!dragItem)
+            continue;
+
+        auto linkDraggingCenter = source.adjustedOrigin;
+        RetainPtr<NSString> title = (NSString *)source.linkTitle;
+        RetainPtr<NSURL> url = (NSURL *)source.linkURL;
+        dragItem.previewProvider = [title, url, linkDraggingCenter] () -> UIDragPreview * {
+            UIURLDragPreviewView *previewView = [UIURLDragPreviewView viewWithTitle:title.get() URL:url.get()];
+            previewView.center = linkDraggingCenter;
+            UIDragPreviewParameters *parameters = [[[UIDragPreviewParameters alloc] initWithTextLineRects:@[ [NSValue valueWithCGRect:previewView.bounds] ]] autorelease];
+            return [[[UIDragPreview alloc] initWithView:previewView parameters:parameters] autorelease];
+        };
+
+        source.possiblyNeedsDragPreviewUpdate = false;
+    }
+}
+
+} // namespace WebKit
+
+#endif // ENABLE(DRAG_SUPPORT) && PLATFORM(IOS)
index 60a9f77bb5dcb713b53af61cd5c47e66fa342b23..9f703d8bf289bfb58b0ed645b54a38286f9454ea 100644 (file)
@@ -28,6 +28,7 @@
 #import "WKContentView.h"
 
 #import "AssistedNodeInformation.h"
+#import "DragDropInteractionState.h"
 #import "EditorState.h"
 #import "GestureTypes.h"
 #import "InteractionInformationAtPosition.h"
@@ -39,7 +40,6 @@
 #import "WKSyntheticClickTapGestureRecognizer.h"
 #import <UIKit/UIView.h>
 #import <WebCore/Color.h>
-#import <WebCore/DragActions.h>
 #import <WebCore/FloatQuad.h>
 #import <wtf/BlockPtr.h>
 #import <wtf/Forward.h>
@@ -118,33 +118,6 @@ typedef std::pair<WebKit::InteractionInformationRequest, InteractionInformationC
 
 namespace WebKit {
 
-#if ENABLE(DRAG_SUPPORT)
-
-struct WKDataInteractionState {
-    RetainPtr<UIImage> image;
-    std::optional<WebCore::TextIndicatorData> indicatorData;
-    CGPoint adjustedOrigin { CGPointZero };
-    CGPoint lastGlobalPosition { CGPointZero };
-    CGRect elementBounds { CGRectZero };
-    BOOL didBeginDragging { NO };
-    BOOL isPerformingOperation { NO };
-    BOOL isAnimatingConcludeEditDrag { NO };
-    BOOL shouldRestoreCalloutBar { NO };
-    RetainPtr<id <UIDragSession>> dragSession;
-    RetainPtr<id <UIDropSession>> dropSession;
-    BlockPtr<void()> dragStartCompletionBlock;
-    BlockPtr<void()> dragCancelSetDownBlock;
-    WebCore::DragSourceAction sourceAction { WebCore::DragSourceActionNone };
-
-    String linkTitle;
-    WebCore::URL linkURL;
-
-    RetainPtr<UIView> visibleContentViewSnapshot;
-    RetainPtr<_UITextDragCaretView> caretView;
-};
-
-#endif // ENABLE(DRAG_SUPPORT)
-
 struct WKSelectionDrawingInfo {
     enum class SelectionType { None, Plugin, Range };
     WKSelectionDrawingInfo();
@@ -256,9 +229,13 @@ struct WKAutoCorrectionData {
     BOOL _needsDeferredEndScrollingSelectionUpdate;
 
 #if ENABLE(DATA_INTERACTION)
-    WebKit::WKDataInteractionState _dataInteractionState;
-    RetainPtr<UIDragInteraction> _dataInteraction;
-    RetainPtr<UIDropInteraction> _dataOperation;
+    WebKit::DragDropInteractionState _dragDropInteractionState;
+    RetainPtr<UIDragInteraction> _dragInteraction;
+    RetainPtr<UIDropInteraction> _dropInteraction;
+    BOOL _shouldRestoreCalloutBarAfterDrop;
+    BOOL _isAnimatingConcludeEditDrag;
+    RetainPtr<UIView> _visibleContentViewSnapshot;
+    RetainPtr<_UITextDragCaretView> _editDropCaretView;
 #endif
 }
 
index 33b8c20213277aea7708168104eb6bc87f904aae..a968961460b07a471054e52de2abb95f03b32e1e 100644 (file)
@@ -3861,7 +3861,7 @@ static bool isAssistableInputType(InputType type)
         // The default behavior is to allow node assistance if the user is interacting or the keyboard is already active.
         shouldShowKeyboard = userIsInteracting || _textSelectionAssistant;
 #if ENABLE(DATA_INTERACTION)
-        shouldShowKeyboard |= _dataInteractionState.isPerformingOperation;
+        shouldShowKeyboard |= _dragDropInteractionState.isPerformingDrop();
 #endif
     }
     if (!shouldShowKeyboard)
@@ -4135,7 +4135,7 @@ static bool isAssistableInputType(InputType type)
     if ([uiDelegate respondsToSelector:@selector(_webView:showCustomSheetForElement:)]) {
         if ([uiDelegate _webView:_webView showCustomSheetForElement:element]) {
 #if ENABLE(DATA_INTERACTION)
-            BOOL shouldCancelAllTouches = !_dataInteractionState.sourceAction;
+            BOOL shouldCancelAllTouches = !_dragDropInteractionState.dragSession();
 #else
             BOOL shouldCancelAllTouches = YES;
 #endif
@@ -4206,7 +4206,7 @@ static BOOL shouldEnableDragInteractionForPolicy(_WKDragInteractionPolicy policy
 
 - (void)_didChangeDragInteractionPolicy
 {
-    [_dataInteraction setEnabled:shouldEnableDragInteractionForPolicy(_webView._dragInteractionPolicy)];
+    [_dragInteraction setEnabled:shouldEnableDragInteractionForPolicy(_webView._dragInteractionPolicy)];
 }
 
 - (NSTimeInterval)dragLiftDelay
@@ -4228,25 +4228,25 @@ static BOOL shouldEnableDragInteractionForPolicy(_WKDragInteractionPolicy policy
 
 - (void)setupDataInteractionDelegates
 {
-    _dataInteraction = adoptNS([[UIDragInteraction alloc] initWithDelegate:self]);
-    _dataOperation = adoptNS([[UIDropInteraction alloc] initWithDelegate:self]);
-    [_dataInteraction _setLiftDelay:self.dragLiftDelay];
-    [_dataInteraction setEnabled:shouldEnableDragInteractionForPolicy(_webView._dragInteractionPolicy)];
+    _dragInteraction = adoptNS([[UIDragInteraction alloc] initWithDelegate:self]);
+    _dropInteraction = adoptNS([[UIDropInteraction alloc] initWithDelegate:self]);
+    [_dragInteraction _setLiftDelay:self.dragLiftDelay];
+    [_dragInteraction setEnabled:shouldEnableDragInteractionForPolicy(_webView._dragInteractionPolicy)];
 
-    [self addInteraction:_dataInteraction.get()];
-    [self addInteraction:_dataOperation.get()];
+    [self addInteraction:_dragInteraction.get()];
+    [self addInteraction:_dropInteraction.get()];
 }
 
 - (void)teardownDataInteractionDelegates
 {
-    if (_dataInteraction)
-        [self removeInteraction:_dataInteraction.get()];
+    if (_dragInteraction)
+        [self removeInteraction:_dragInteraction.get()];
 
-    if (_dataOperation)
-        [self removeInteraction:_dataOperation.get()];
+    if (_dropInteraction)
+        [self removeInteraction:_dropInteraction.get()];
 
-    _dataInteraction = nil;
-    _dataOperation = nil;
+    _dragInteraction = nil;
+    _dropInteraction = nil;
 
     [self cleanUpDragSourceSessionState];
 }
@@ -4255,124 +4255,41 @@ static BOOL shouldEnableDragInteractionForPolicy(_WKDragInteractionPolicy policy
 {
     ASSERT(item.sourceAction != DragSourceActionNone);
 
-    _dataInteractionState.image = adoptNS([[UIImage alloc] initWithCGImage:image.get() scale:_page->deviceScaleFactor() orientation:UIImageOrientationUp]);
-    _dataInteractionState.indicatorData = item.image.indicatorData();
-    _dataInteractionState.sourceAction = static_cast<DragSourceAction>(item.sourceAction);
-    _dataInteractionState.adjustedOrigin = item.eventPositionInContentCoordinates;
-    _dataInteractionState.elementBounds = item.elementBounds;
-    _dataInteractionState.linkTitle = item.title.isEmpty() ? nil : (NSString *)item.title;
-    _dataInteractionState.linkURL = item.url.isEmpty() ? nil : (NSURL *)item.url;
+    auto dragImage = adoptNS([[UIImage alloc] initWithCGImage:image.get() scale:_page->deviceScaleFactor() orientation:UIImageOrientationUp]);
+    _dragDropInteractionState.stageDragItem(item, dragImage.get());
 }
 
 - (void)_didHandleStartDataInteractionRequest:(BOOL)started
 {
-    BlockPtr<void()> savedCompletionBlock = _dataInteractionState.dragStartCompletionBlock;
-    _dataInteractionState.dragStartCompletionBlock = nil;
+    BlockPtr<void()> savedCompletionBlock = _dragDropInteractionState.takeDragStartCompletionBlock();
     ASSERT(savedCompletionBlock);
 
     RELEASE_LOG(DragAndDrop, "Handling drag start request (started: %d, completion block: %p)", started, savedCompletionBlock.get());
     if (savedCompletionBlock)
         savedCompletionBlock();
 
-    if (![_dataInteractionState.dragSession items].count) {
-        CGPoint adjustedOrigin = _dataInteractionState.adjustedOrigin;
+    if (!_dragDropInteractionState.dragSession().items.count) {
+        auto positionForDragEnd = roundedIntPoint(_dragDropInteractionState.adjustedPositionForDragEnd());
         [self cleanUpDragSourceSessionState];
         if (started) {
             // A client of the Objective C SPI or UIKit might have prevented the drag from beginning entirely in the UI process, in which case
             // we need to balance the `dragstart` event with a `dragend`.
-            _page->dragEnded(roundedIntPoint(adjustedOrigin), roundedIntPoint([self convertPoint:adjustedOrigin toView:self.window]), DragOperationNone);
+            _page->dragEnded(positionForDragEnd, positionForDragEnd, DragOperationNone);
         }
     }
 }
 
-static RetainPtr<UIImage> uiImageForImage(RefPtr<Image> image)
-{
-    if (!image)
-        return nullptr;
-
-    auto cgImage = image->nativeImage();
-    if (!cgImage)
-        return nullptr;
-
-    return adoptNS([[UIImage alloc] initWithCGImage:cgImage.get()]);
-}
-
-static BOOL shouldUseTextIndicatorToCreatePreviewForDragAction(DragSourceAction action)
-{
-    if (action & (DragSourceActionLink | DragSourceActionSelection))
-        return YES;
-
-#if ENABLE(ATTACHMENT_ELEMENT)
-    if (action & DragSourceActionAttachment)
-        return YES;
-#endif
-
-    return NO;
-}
-
-- (RetainPtr<UITargetedDragPreview>)dragPreviewForImage:(UIImage *)image frameInRootViewCoordinates:(const FloatRect&)frame clippingRectsInFrameCoordinates:(const Vector<FloatRect>&)clippingRects backgroundColor:(UIColor *)backgroundColor
-{
-    if (frame.isEmpty() || !image)
-        return nullptr;
-
-    UIView *container = [self unscaledView];
-    FloatRect frameInContainerCoordinates;
-    NSMutableArray *clippingRectValuesInFrameCoordinates = [NSMutableArray arrayWithCapacity:clippingRects.size()];
-
-    frameInContainerCoordinates = [self convertRect:frame toView:container];
-    if (frameInContainerCoordinates.isEmpty())
-        return nullptr;
-
-    float widthScalingRatio = frameInContainerCoordinates.width() / frame.width();
-    float heightScalingRatio = frameInContainerCoordinates.height() / frame.height();
-    for (auto rect : clippingRects) {
-        rect.scale(widthScalingRatio, heightScalingRatio);
-        [clippingRectValuesInFrameCoordinates addObject:[NSValue valueWithCGRect:rect]];
-    }
-
-    auto imageView = adoptNS([[UIImageView alloc] initWithImage:image]);
-    [imageView setFrame:frameInContainerCoordinates];
-
-    RetainPtr<UIDragPreviewParameters> parameters;
-    if (clippingRectValuesInFrameCoordinates.count)
-        parameters = adoptNS([[UIDragPreviewParameters alloc] initWithTextLineRects:clippingRectValuesInFrameCoordinates]);
-    else
-        parameters = adoptNS([[UIDragPreviewParameters alloc] init]);
-
-    if (backgroundColor)
-        [parameters setBackgroundColor:backgroundColor];
-
-    CGPoint centerInContainerCoordinates = { CGRectGetMidX(frameInContainerCoordinates), CGRectGetMidY(frameInContainerCoordinates) };
-    auto target = adoptNS([[UIDragPreviewTarget alloc] initWithContainer:container center:centerInContainerCoordinates]);
-    auto dragPreview = adoptNS([[UITargetedDragPreview alloc] initWithView:imageView.get() parameters:parameters.get() target:target.get()]);
-    return dragPreview;
-}
-
-- (RetainPtr<UITargetedDragPreview>)dragPreviewForCurrentDataInteractionState
-{
-    auto action = _dataInteractionState.sourceAction;
-    if (action & DragSourceActionImage && _dataInteractionState.image) {
-        Vector<FloatRect> emptyClippingRects;
-        return [self dragPreviewForImage:_dataInteractionState.image.get() frameInRootViewCoordinates:_dataInteractionState.elementBounds clippingRectsInFrameCoordinates:emptyClippingRects backgroundColor:nil];
-    }
-
-    if (shouldUseTextIndicatorToCreatePreviewForDragAction(action) && _dataInteractionState.indicatorData) {
-        auto indicator = _dataInteractionState.indicatorData.value();
-        return [self dragPreviewForImage:uiImageForImage(indicator.contentImage).get() frameInRootViewCoordinates:indicator.textBoundingRectInRootViewCoordinates clippingRectsInFrameCoordinates:indicator.textRectsInBoundingRectCoordinates backgroundColor:[UIColor colorWithCGColor:cachedCGColor(indicator.estimatedBackgroundColor)]];
-    }
-
-    return nil;
-}
-
 - (void)computeClientAndGlobalPointsForDropSession:(id <UIDropSession>)session outClientPoint:(CGPoint *)outClientPoint outGlobalPoint:(CGPoint *)outGlobalPoint
 {
+    // FIXME: This makes the behavior of drag events on iOS consistent with other synthetic mouse events on iOS (see WebPage::completeSyntheticClick).
+    // However, we should experiment with making the client position relative to the window and the global position in document coordinates. See
+    // https://bugs.webkit.org/show_bug.cgi?id=173855 for more details.
+    auto locationInContentView = [session locationInView:self];
     if (outClientPoint)
-        *outClientPoint = [session locationInView:self];
+        *outClientPoint = locationInContentView;
 
-    if (outGlobalPoint) {
-        UIWindow *window = self.window;
-        *outGlobalPoint = window ? [session locationInView:window] : _dataInteractionState.lastGlobalPosition;
-    }
+    if (outGlobalPoint)
+        *outGlobalPoint = locationInContentView;
 }
 
 static UIDropOperation dropOperationForWebCoreDragOperation(DragOperation operation)
@@ -4405,27 +4322,23 @@ static UIDropOperation dropOperationForWebCoreDragOperation(DragOperation operat
         [[WebItemProviderPasteboard sharedInstance] setItemProviders:nil];
     }
 
-    if (auto completionBlock = _dataInteractionState.dragCancelSetDownBlock) {
-        _dataInteractionState.dragCancelSetDownBlock = nil;
-        completionBlock();
-    }
-
-    if (auto completionBlock = _dataInteractionState.dragStartCompletionBlock) {
-        // If the previous drag session is still initializing, we need to ensure that its completion block is called to prevent UIKit from getting out of state.
-        _dataInteractionState.dragStartCompletionBlock = nil;
-        completionBlock();
-    }
-
+    [[WebItemProviderPasteboard sharedInstance] stageRegistrationList:nil];
     [self _restoreCalloutBarIfNeeded];
-    [_dataInteractionState.caretView remove];
-    [_dataInteractionState.visibleContentViewSnapshot removeFromSuperview];
 
-    _dataInteractionState = { };
+    [_visibleContentViewSnapshot removeFromSuperview];
+    _visibleContentViewSnapshot = nil;
+    [_editDropCaretView remove];
+    _editDropCaretView = nil;
+    _isAnimatingConcludeEditDrag = NO;
+    _shouldRestoreCalloutBarAfterDrop = NO;
+
+    _dragDropInteractionState.dragAndDropSessionsDidEnd();
+    _dragDropInteractionState = { };
 }
 
 static NSArray<UIItemProvider *> *extractItemProvidersFromDragItems(NSArray<UIDragItem *> *dragItems)
 {
-    __block NSMutableArray<UIItemProvider *> *providers = [NSMutableArray array];
+    NSMutableArray<UIItemProvider *> *providers = [NSMutableArray array];
     for (UIDragItem *item in dragItems) {
         RetainPtr<UIItemProvider> provider = item.itemProvider;
         if (provider)
@@ -4457,9 +4370,9 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
     [unselectedContentSnapshot setFrame:data->contentImageWithoutSelectionRectInRootViewCoordinates];
 
     RetainPtr<WKContentView> protectedSelf = self;
-    RetainPtr<UIView> visibleContentViewSnapshot = adoptNS(_dataInteractionState.visibleContentViewSnapshot.leakRef());
+    RetainPtr<UIView> visibleContentViewSnapshot = adoptNS(_visibleContentViewSnapshot.leakRef());
 
-    _dataInteractionState.isAnimatingConcludeEditDrag = YES;
+    _isAnimatingConcludeEditDrag = YES;
     [self insertSubview:unselectedContentSnapshot.get() belowSubview:visibleContentViewSnapshot.get()];
     [UIView animateWithDuration:0.25 animations:^() {
         [visibleContentViewSnapshot setAlpha:0];
@@ -4478,37 +4391,20 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 {
     RELEASE_LOG(DragAndDrop, "Finished performing drag controller operation (handled: %d)", handled);
     [[WebItemProviderPasteboard sharedInstance] decrementPendingOperationCount];
-    RetainPtr<id <UIDropSession>> dropSession = _dataInteractionState.dropSession;
+    id <UIDropSession> dropSession = _dragDropInteractionState.dropSession();
     if ([self.webViewUIDelegate respondsToSelector:@selector(_webView:dataInteractionOperationWasHandled:forSession:itemProviders:)])
-        [self.webViewUIDelegate _webView:_webView dataInteractionOperationWasHandled:handled forSession:dropSession.get() itemProviders:[WebItemProviderPasteboard sharedInstance].itemProviders];
+        [self.webViewUIDelegate _webView:_webView dataInteractionOperationWasHandled:handled forSession:dropSession itemProviders:[WebItemProviderPasteboard sharedInstance].itemProviders];
 
-    if (!_dataInteractionState.isAnimatingConcludeEditDrag)
+    if (!_isAnimatingConcludeEditDrag)
         self.suppressAssistantSelectionView = NO;
 
     CGPoint global;
     CGPoint client;
-    [self computeClientAndGlobalPointsForDropSession:dropSession.get() outClientPoint:&client outGlobalPoint:&global];
+    [self computeClientAndGlobalPointsForDropSession:dropSession outClientPoint:&client outGlobalPoint:&global];
     [self cleanUpDragSourceSessionState];
     _page->dragEnded(roundedIntPoint(client), roundedIntPoint(global), _page->currentDragOperation());
 }
 
-- (void)_transitionDragPreviewToImageIfNecessary:(id <UIDragSession>)session
-{
-    if (_dataInteractionState.sourceAction & DragSourceActionImage || !(_dataInteractionState.sourceAction & DragSourceActionLink))
-        return;
-
-    auto linkDraggingCenter = _dataInteractionState.adjustedOrigin;
-    RetainPtr<NSString> title = (NSString *)_dataInteractionState.linkTitle;
-    RetainPtr<NSURL> url = (NSURL *)_dataInteractionState.linkURL;
-    session.items.firstObject.previewProvider = [title, url, linkDraggingCenter] () -> UIDragPreview * {
-        UIURLDragPreviewView *previewView = [UIURLDragPreviewView viewWithTitle:title.get() URL:url.get()];
-        previewView.center = linkDraggingCenter;
-
-        UIDragPreviewParameters *parameters = [[[UIDragPreviewParameters alloc] initWithTextLineRects:@[ [NSValue valueWithCGRect:previewView.bounds] ]] autorelease];
-        return [[[UIDragPreview alloc] initWithView:previewView parameters:parameters] autorelease];
-    };
-}
-
 - (void)_didChangeDataInteractionCaretRect:(CGRect)previousRect currentRect:(CGRect)rect
 {
     BOOL previousRectIsEmpty = CGRectIsEmpty(previousRect);
@@ -4517,18 +4413,18 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
         return;
 
     if (previousRectIsEmpty) {
-        _dataInteractionState.caretView = adoptNS([[_UITextDragCaretView alloc] initWithTextInputView:self]);
-        [_dataInteractionState.caretView insertAtPosition:[WKTextPosition textPositionWithRect:rect]];
+        _editDropCaretView = adoptNS([[_UITextDragCaretView alloc] initWithTextInputView:self]);
+        [_editDropCaretView insertAtPosition:[WKTextPosition textPositionWithRect:rect]];
         return;
     }
 
     if (currentRectIsEmpty) {
-        [_dataInteractionState.caretView remove];
-        _dataInteractionState.caretView = nil;
+        [_editDropCaretView remove];
+        _editDropCaretView = nil;
         return;
     }
 
-    [_dataInteractionState.caretView updateToPosition:[WKTextPosition textPositionWithRect:rect]];
+    [_editDropCaretView updateToPosition:[WKTextPosition textPositionWithRect:rect]];
 }
 
 - (WKDragDestinationAction)_dragDestinationActionForDropSession:(id <UIDropSession>)session
@@ -4542,20 +4438,20 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 
 - (id <UIDragDropSession>)currentDragOrDropSession
 {
-    if (_dataInteractionState.dropSession)
-        return _dataInteractionState.dropSession.get();
-    return _dataInteractionState.dragSession.get();
+    if (_dragDropInteractionState.dropSession())
+        return _dragDropInteractionState.dropSession();
+    return _dragDropInteractionState.dragSession();
 }
 
 - (void)_restoreCalloutBarIfNeeded
 {
-    if (!_dataInteractionState.shouldRestoreCalloutBar)
+    if (!_shouldRestoreCalloutBarAfterDrop)
         return;
 
     // FIXME: This SPI should be renamed in UIKit to reflect a more general purpose of revealing hidden interaction assistant controls.
     [_webSelectionAssistant didEndScrollingOverflow];
     [_textSelectionAssistant didEndScrollingOverflow];
-    _dataInteractionState.shouldRestoreCalloutBar = NO;
+    _shouldRestoreCalloutBarAfterDrop = NO;
 }
 
 #pragma mark - UIDragInteractionDelegate
@@ -4595,9 +4491,9 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 
     [self cleanUpDragSourceSessionState];
 
+    _dragDropInteractionState.prepareForDragSession(session, completion);
+
     auto dragOrigin = roundedIntPoint([session locationInView:self]);
-    _dataInteractionState.dragStartCompletionBlock = completion;
-    _dataInteractionState.dragSession = session;
     _page->requestStartDataInteraction(dragOrigin, roundedIntPoint([self convertPoint:dragOrigin toView:self.window]));
 
     RELEASE_LOG(DragAndDrop, "Drag session requested: %p at origin: {%d, %d}", session, dragOrigin.x(), dragOrigin.y());
@@ -4605,57 +4501,58 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 
 - (NSArray<UIDragItem *> *)dragInteraction:(UIDragInteraction *)interaction itemsForBeginningSession:(id <UIDragSession>)session
 {
+    ASSERT(interaction == _dragInteraction);
     RELEASE_LOG(DragAndDrop, "Drag items requested for session: %p", session);
-    if (_dataInteractionState.dragSession != session) {
-        RELEASE_LOG(DragAndDrop, "Drag session failed: %p (delegate session does not match %p)", session, _dataInteractionState.dragSession.get());
+    if (_dragDropInteractionState.dragSession() != session) {
+        RELEASE_LOG(DragAndDrop, "Drag session failed: %p (delegate session does not match %p)", session, _dragDropInteractionState.dragSession());
         return @[ ];
     }
 
-    if (_dataInteractionState.sourceAction == DragSourceActionNone) {
-        RELEASE_LOG(DragAndDrop, "Drag session failed: %p (no drag source action)", session);
+    if (!_dragDropInteractionState.hasStagedDragSource()) {
+        RELEASE_LOG(DragAndDrop, "Drag session failed: %p (missing staged drag source)", session);
         return @[ ];
     }
 
-    WebItemProviderPasteboard *draggingPasteboard = [WebItemProviderPasteboard sharedInstance];
-    ASSERT(interaction == _dataInteraction);
-    NSUInteger numberOfItems = draggingPasteboard.numberOfItems;
-    if (!numberOfItems) {
+    auto stagedDragSource = _dragDropInteractionState.stagedDragSource();
+    WebItemProviderRegistrationInfoList *registrationList = [[WebItemProviderPasteboard sharedInstance] takeRegistrationList];
+    UIItemProvider *defaultItemProvider = registrationList.itemProvider;
+    if (!defaultItemProvider) {
         RELEASE_LOG(DragAndDrop, "Drag session failed: %p (no item providers generated before adjustment)", session);
         _page->dragCancelled();
+        _dragDropInteractionState.clearStagedDragSource();
         return @[ ];
     }
 
     // Give internal clients such as Mail one final chance to augment the contents of each UIItemProvider before sending the drag items off to UIKit.
+    NSArray *adjustedItemProviders;
     id <WKUIDelegatePrivate> uiDelegate = self.webViewUIDelegate;
     if ([uiDelegate respondsToSelector:@selector(_webView:adjustedDataInteractionItemProvidersForItemProvider:representingObjects:additionalData:)]) {
-        NSMutableArray *adjustedItemProviders = [NSMutableArray array];
-        for (NSUInteger itemIndex = 0; itemIndex < numberOfItems; ++itemIndex) {
-            WebItemProviderRegistrationInfoList *infoList = [draggingPasteboard registrationInfoAtIndex:itemIndex];
-            auto representingObjects = adoptNS([[NSMutableArray alloc] init]);
-            auto additionalData = adoptNS([[NSMutableDictionary alloc] init]);
-            [infoList enumerateItems:[representingObjects, additionalData] (WebItemProviderRegistrationInfo *item, NSUInteger) {
-                if (item.representingObject)
-                    [representingObjects addObject:item.representingObject];
-                if (item.typeIdentifier && item.data)
-                    [additionalData setObject:item.data forKey:item.typeIdentifier];
-            }];
-            if (NSArray *replacementItemProviders = [uiDelegate _webView:_webView adjustedDataInteractionItemProvidersForItemProvider:[draggingPasteboard itemProviderAtIndex:itemIndex] representingObjects:representingObjects.get() additionalData:additionalData.get()])
-                [adjustedItemProviders addObjectsFromArray:replacementItemProviders];
-        }
-        draggingPasteboard.itemProviders = adjustedItemProviders;
-    } else if ([uiDelegate respondsToSelector:@selector(_webView:adjustedDataInteractionItemProviders:)])
-        draggingPasteboard.itemProviders = [uiDelegate _webView:_webView adjustedDataInteractionItemProviders:draggingPasteboard.itemProviders];
+        auto representingObjects = adoptNS([[NSMutableArray alloc] init]);
+        auto additionalData = adoptNS([[NSMutableDictionary alloc] init]);
+        [registrationList enumerateItems:[representingObjects, additionalData] (WebItemProviderRegistrationInfo *item, NSUInteger) {
+            if (item.representingObject)
+                [representingObjects addObject:item.representingObject];
+            if (item.typeIdentifier && item.data)
+                [additionalData setObject:item.data forKey:item.typeIdentifier];
+        }];
+        adjustedItemProviders = [uiDelegate _webView:_webView adjustedDataInteractionItemProvidersForItemProvider:defaultItemProvider representingObjects:representingObjects.get() additionalData:additionalData.get()];
+    } else
+        adjustedItemProviders = @[ defaultItemProvider ];
 
-    __block RetainPtr<NSMutableArray> itemsForDragInteraction = [NSMutableArray array];
-    [draggingPasteboard enumerateItemProvidersWithBlock:^(UIItemProvider *itemProvider, NSUInteger index, BOOL *stop) {
-        [itemsForDragInteraction addObject:[[[UIDragItem alloc] initWithItemProvider:itemProvider] autorelease]];
-    }];
+    NSMutableArray *dragItems = [NSMutableArray arrayWithCapacity:adjustedItemProviders.count];
+    for (UIItemProvider *itemProvider in adjustedItemProviders) {
+        auto item = adoptNS([[UIDragItem alloc] initWithItemProvider:itemProvider]);
+        [item _setPrivateLocalContext:@(stagedDragSource.itemIdentifier)];
+        [dragItems addObject:item.autorelease()];
+    }
 
-    if (![itemsForDragInteraction count])
+    if (![dragItems count])
         _page->dragCancelled();
 
-    RELEASE_LOG(DragAndDrop, "Drag session: %p starting with %tu items", session, [itemsForDragInteraction count]);
-    return itemsForDragInteraction.get();
+    RELEASE_LOG(DragAndDrop, "Drag session: %p starting with %tu items", session, [dragItems count]);
+    _dragDropInteractionState.clearStagedDragSource([dragItems count] ? DragDropInteractionState::DidBecomeActive::Yes : DragDropInteractionState::DidBecomeActive::No);
+
+    return dragItems;
 }
 
 - (UITargetedDragPreview *)dragInteraction:(UIDragInteraction *)interaction previewForLiftingItem:(UIDragItem *)item session:(id <UIDragSession>)session
@@ -4666,27 +4563,26 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
         if (overridenPreview)
             return overridenPreview;
     }
-    return self.dragPreviewForCurrentDataInteractionState.autorelease();
+    return _dragDropInteractionState.previewForDragItem(item, self, self.unscaledView);
 }
 
 - (void)dragInteraction:(UIDragInteraction *)interaction willAnimateLiftWithAnimator:(id <UIDragAnimating>)animator session:(id <UIDragSession>)session
 {
-    if (!_dataInteractionState.shouldRestoreCalloutBar && (_dataInteractionState.sourceAction & DragSourceActionSelection)) {
+    if (!_shouldRestoreCalloutBarAfterDrop && _dragDropInteractionState.anyActiveDragSourceIs(DragSourceActionSelection)) {
         // FIXME: This SPI should be renamed in UIKit to reflect a more general purpose of hiding interaction assistant controls.
         [_webSelectionAssistant willStartScrollingOverflow];
         [_textSelectionAssistant willStartScrollingOverflow];
-        _dataInteractionState.shouldRestoreCalloutBar = YES;
+        _shouldRestoreCalloutBarAfterDrop = YES;
     }
 
-    auto adjustedOrigin = _dataInteractionState.adjustedOrigin;
+    auto positionForDragEnd = roundedIntPoint(_dragDropInteractionState.adjustedPositionForDragEnd());
     RetainPtr<WKContentView> protectedSelf(self);
-    [animator addCompletion:[session, adjustedOrigin, protectedSelf, page = _page] (UIViewAnimatingPosition finalPosition) {
+    [animator addCompletion:[session, positionForDragEnd, protectedSelf, page = _page] (UIViewAnimatingPosition finalPosition) {
         if (finalPosition == UIViewAnimatingPositionStart) {
             RELEASE_LOG(DragAndDrop, "Drag session ended at start: %p", session);
             // The lift was canceled, so -dropInteraction:sessionDidEnd: will never be invoked. This is the last chance to clean up.
             [protectedSelf cleanUpDragSourceSessionState];
-            auto originInWindowCoordinates = [protectedSelf convertPoint:adjustedOrigin toView:[protectedSelf window]];
-            page->dragEnded(roundedIntPoint(adjustedOrigin), roundedIntPoint(originInWindowCoordinates), DragOperationNone);
+            page->dragEnded(positionForDragEnd, positionForDragEnd, DragOperationNone);
         }
     }];
 }
@@ -4699,16 +4595,13 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
         [uiDelegate _webView:_webView dataInteraction:interaction sessionWillBegin:session];
 
     [_actionSheetAssistant cleanupSheet];
-
-    _dataInteractionState.didBeginDragging = YES;
-    [self _transitionDragPreviewToImageIfNecessary:session];
-
+    _dragDropInteractionState.dragSessionWillBegin();
     _page->didStartDrag();
 }
 
 - (void)dragInteraction:(UIDragInteraction *)interaction session:(id <UIDragSession>)session didEndWithOperation:(UIDropOperation)operation
 {
-    RELEASE_LOG(DragAndDrop, "Drag session ended: %p (with operation: %tu, performing operation: %d, began dragging: %d)", session, operation, _dataInteractionState.isPerformingOperation, _dataInteractionState.didBeginDragging);
+    RELEASE_LOG(DragAndDrop, "Drag session ended: %p (with operation: %tu, performing operation: %d, began dragging: %d)", session, operation, _dragDropInteractionState.isPerformingDrop(), _dragDropInteractionState.didBeginDragging());
 
     [self _restoreCalloutBarIfNeeded];
 
@@ -4716,11 +4609,11 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
     if ([uiDelegate respondsToSelector:@selector(_webView:dataInteraction:session:didEndWithOperation:)])
         [uiDelegate _webView:_webView dataInteraction:interaction session:session didEndWithOperation:operation];
 
-    if (_dataInteractionState.isPerformingOperation)
+    if (_dragDropInteractionState.isPerformingDrop())
         return;
 
     [self cleanUpDragSourceSessionState];
-    _page->dragEnded(roundedIntPoint(_dataInteractionState.adjustedOrigin), roundedIntPoint([self convertPoint:_dataInteractionState.adjustedOrigin toView:self.window]), operation);
+    _page->dragEnded(roundedIntPoint(_dragDropInteractionState.adjustedPositionForDragEnd()), roundedIntPoint(_dragDropInteractionState.adjustedPositionForDragEnd()), operation);
 }
 
 - (UITargetedDragPreview *)dragInteraction:(UIDragInteraction *)interaction previewForCancellingItem:(UIDragItem *)item withDefault:(UITargetedDragPreview *)defaultPreview
@@ -4731,12 +4624,12 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
         if (overridenPreview)
             return overridenPreview;
     }
-    return self.dragPreviewForCurrentDataInteractionState.autorelease();
+    return _dragDropInteractionState.previewForDragItem(item, self, self.unscaledView);
 }
 
 - (BOOL)_dragInteraction:(UIDragInteraction *)interaction item:(UIDragItem *)item shouldDelaySetDownAnimationWithCompletion:(void(^)(void))completion
 {
-    _dataInteractionState.dragCancelSetDownBlock = completion;
+    _dragDropInteractionState.dragSessionWillDelaySetDownAnimation(completion);
     return YES;
 }
 
@@ -4744,8 +4637,7 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 {
     [animator addCompletion:[protectedSelf = retainPtr(self), page = _page] (UIViewAnimatingPosition finalPosition) {
         page->dragCancelled();
-        if (auto completion = protectedSelf->_dataInteractionState.dragCancelSetDownBlock) {
-            protectedSelf->_dataInteractionState.dragCancelSetDownBlock = nil;
+        if (auto completion = protectedSelf->_dragDropInteractionState.takeDragCancelSetDownBlock()) {
             page->callAfterNextPresentationUpdate([completion] (CallbackBase::Error) {
                 completion();
             });
@@ -4776,14 +4668,12 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 - (void)dropInteraction:(UIDropInteraction *)interaction sessionDidEnter:(id <UIDropSession>)session
 {
     RELEASE_LOG(DragAndDrop, "Drop session entered: %p with %tu items", session, session.items.count);
-    _dataInteractionState.dropSession = session;
-
-    [[WebItemProviderPasteboard sharedInstance] setItemProviders:extractItemProvidersFromDropSession(session)];
-
     auto dragData = [self dragDataForDropSession:session dragDestinationAction:[self _dragDestinationActionForDropSession:session]];
 
+    _dragDropInteractionState.dropSessionDidEnterOrUpdate(session, dragData);
+
+    [[WebItemProviderPasteboard sharedInstance] setItemProviders:extractItemProvidersFromDropSession(session)];
     _page->dragEntered(dragData, "data interaction pasteboard");
-    _dataInteractionState.lastGlobalPosition = dragData.globalPosition();
 }
 
 - (UIDropProposal *)dropInteraction:(UIDropInteraction *)interaction sessionDidUpdate:(id <UIDropSession>)session
@@ -4792,7 +4682,7 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 
     auto dragData = [self dragDataForDropSession:session dragDestinationAction:[self _dragDestinationActionForDropSession:session]];
     _page->dragUpdated(dragData, "data interaction pasteboard");
-    _dataInteractionState.lastGlobalPosition = dragData.globalPosition();
+    _dragDropInteractionState.dropSessionDidEnterOrUpdate(session, dragData);
 
     NSUInteger operation = dropOperationForWebCoreDragOperation(_page->currentDragOperation());
     if ([self.webViewUIDelegate respondsToSelector:@selector(_webView:willUpdateDataInteractionOperationToOperation:forSession:)])
@@ -4810,7 +4700,7 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
     _page->dragExited(dragData, "data interaction pasteboard");
     _page->resetCurrentDragInformation();
 
-    _dataInteractionState.dropSession = nil;
+    _dragDropInteractionState.dropSessionDidExit();
 }
 
 - (void)dropInteraction:(UIDropInteraction *)interaction performDrop:(id <UIDropSession>)session
@@ -4828,9 +4718,10 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
             return;
     }
 
+    _dragDropInteractionState.dropSessionWillPerformDrop();
+
     [[WebItemProviderPasteboard sharedInstance] setItemProviders:itemProviders];
     [[WebItemProviderPasteboard sharedInstance] incrementPendingOperationCount];
-    _dataInteractionState.isPerformingOperation = YES;
     auto dragData = [self dragDataForDropSession:session dragDestinationAction:WKDragDestinationActionAny];
 
     RELEASE_LOG(DragAndDrop, "Loading data from %tu item providers for session: %p", itemProviders.count, session);
@@ -4851,11 +4742,11 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
         retainedSelf->_page->createSandboxExtensionsIfNeeded(filenames, sandboxExtensionHandle, sandboxExtensionForUpload);
         retainedSelf->_page->performDragOperation(capturedDragData, "data interaction pasteboard", sandboxExtensionHandle, sandboxExtensionForUpload);
 
-        retainedSelf->_dataInteractionState.visibleContentViewSnapshot = [retainedSelf snapshotViewAfterScreenUpdates:NO];
+        retainedSelf->_visibleContentViewSnapshot = [retainedSelf snapshotViewAfterScreenUpdates:NO];
         [retainedSelf setSuppressAssistantSelectionView:YES];
         [UIView performWithoutAnimation:[retainedSelf] {
-            [retainedSelf->_dataInteractionState.visibleContentViewSnapshot setFrame:[retainedSelf bounds]];
-            [retainedSelf addSubview:retainedSelf->_dataInteractionState.visibleContentViewSnapshot.get()];
+            [retainedSelf->_visibleContentViewSnapshot setFrame:[retainedSelf bounds]];
+            [retainedSelf addSubview:retainedSelf->_visibleContentViewSnapshot.get()];
         }];
     }];
 }
@@ -4877,9 +4768,16 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 
 - (void)dropInteraction:(UIDropInteraction *)interaction sessionDidEnd:(id <UIDropSession>)session
 {
-    RELEASE_LOG(DragAndDrop, "Drop session ended: %p (performing operation: %d, began dragging: %d)", session, _dataInteractionState.isPerformingOperation, _dataInteractionState.didBeginDragging);
-    if (_dataInteractionState.isPerformingOperation || _dataInteractionState.didBeginDragging)
+    RELEASE_LOG(DragAndDrop, "Drop session ended: %p (performing operation: %d, began dragging: %d)", session, _dragDropInteractionState.isPerformingDrop(), _dragDropInteractionState.didBeginDragging());
+    if (_dragDropInteractionState.isPerformingDrop()) {
+        // In the case where we are performing a drop, wait until after the drop is handled in the web process to reset drag and drop interaction state.
         return;
+    }
+
+    if (_dragDropInteractionState.didBeginDragging()) {
+        // In the case where the content view is a source of drag items, wait until -dragInteraction:session:didEndWithOperation: to reset drag and drop interaction state.
+        return;
+    }
 
     CGPoint global;
     CGPoint client;
@@ -4892,42 +4790,42 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 
 - (void)_simulateDataInteractionEntered:(id)session
 {
-    [self dropInteraction:_dataOperation.get() sessionDidEnter:session];
+    [self dropInteraction:_dropInteraction.get() sessionDidEnter:session];
 }
 
 - (NSUInteger)_simulateDataInteractionUpdated:(id)session
 {
-    return [self dropInteraction:_dataOperation.get() sessionDidUpdate:session].operation;
+    return [self dropInteraction:_dropInteraction.get() sessionDidUpdate:session].operation;
 }
 
 - (void)_simulateDataInteractionEnded:(id)session
 {
-    [self dropInteraction:_dataOperation.get() sessionDidEnd:session];
+    [self dropInteraction:_dropInteraction.get() sessionDidEnd:session];
 }
 
 - (void)_simulateDataInteractionPerformOperation:(id)session
 {
-    [self dropInteraction:_dataOperation.get() performDrop:session];
+    [self dropInteraction:_dropInteraction.get() performDrop:session];
 }
 
 - (void)_simulateDataInteractionSessionDidEnd:(id)session
 {
-    [self dragInteraction:_dataInteraction.get() session:session didEndWithOperation:UIDropOperationCopy];
+    [self dragInteraction:_dragInteraction.get() session:session didEndWithOperation:UIDropOperationCopy];
 }
 
 - (void)_simulateWillBeginDataInteractionWithSession:(id)session
 {
-    [self dragInteraction:_dataInteraction.get() sessionWillBegin:session];
+    [self dragInteraction:_dragInteraction.get() sessionWillBegin:session];
 }
 
 - (NSArray *)_simulatedItemsForSession:(id)session
 {
-    return [self dragInteraction:_dataInteraction.get() itemsForBeginningSession:session];
+    return [self dragInteraction:_dragInteraction.get() itemsForBeginningSession:session];
 }
 
 - (void)_simulatePrepareForDataInteractionSession:(id)session completion:(dispatch_block_t)completion
 {
-    [self _dragInteraction:_dataInteraction.get() prepareForSession:session completion:completion];
+    [self _dragInteraction:_dragInteraction.get() prepareForSession:session completion:completion];
 }
 
 #endif
index c180da2fe13b7c21558a682cffde7f7f2fbf05a6..a2a285e4d3d0b8adc02c6e79cd2a8d029424ad0e 100644 (file)
                F409BA181E6E64BC009DA28E /* WKDragDestinationAction.h in Headers */ = {isa = PBXBuildFile; fileRef = F409BA171E6E64B3009DA28E /* WKDragDestinationAction.h */; settings = {ATTRIBUTES = (Private, ); }; };
                F44DFEB21E9E752F0038D196 /* WebIconUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = F44DFEB01E9E752F0038D196 /* WebIconUtilities.h */; };
                F44DFEB31E9E752F0038D196 /* WebIconUtilities.mm in Sources */ = {isa = PBXBuildFile; fileRef = F44DFEB11E9E752F0038D196 /* WebIconUtilities.mm */; };
+               F496A4311F58A272004C1757 /* DragDropInteractionState.h in Headers */ = {isa = PBXBuildFile; fileRef = F496A42F1F58A272004C1757 /* DragDropInteractionState.h */; };
+               F496A4321F58A272004C1757 /* DragDropInteractionState.mm in Sources */ = {isa = PBXBuildFile; fileRef = F496A4301F58A272004C1757 /* DragDropInteractionState.mm */; };
                F6113E25126CE1820057D0A7 /* APIUserContentURLPattern.h in Headers */ = {isa = PBXBuildFile; fileRef = F6113E24126CE1820057D0A7 /* APIUserContentURLPattern.h */; };
                F6113E28126CE19B0057D0A7 /* WKUserContentURLPattern.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F6113E26126CE19B0057D0A7 /* WKUserContentURLPattern.cpp */; };
                F6113E29126CE19B0057D0A7 /* WKUserContentURLPattern.h in Headers */ = {isa = PBXBuildFile; fileRef = F6113E27126CE19B0057D0A7 /* WKUserContentURLPattern.h */; settings = {ATTRIBUTES = (Private, ); }; };
                F409BA171E6E64B3009DA28E /* WKDragDestinationAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKDragDestinationAction.h; sourceTree = "<group>"; };
                F44DFEB01E9E752F0038D196 /* WebIconUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebIconUtilities.h; path = ios/WebIconUtilities.h; sourceTree = "<group>"; };
                F44DFEB11E9E752F0038D196 /* WebIconUtilities.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = WebIconUtilities.mm; path = ios/WebIconUtilities.mm; sourceTree = "<group>"; };
+               F496A42F1F58A272004C1757 /* DragDropInteractionState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DragDropInteractionState.h; path = ios/DragDropInteractionState.h; sourceTree = "<group>"; };
+               F496A4301F58A272004C1757 /* DragDropInteractionState.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = DragDropInteractionState.mm; path = ios/DragDropInteractionState.mm; sourceTree = "<group>"; };
                F6113E24126CE1820057D0A7 /* APIUserContentURLPattern.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APIUserContentURLPattern.h; sourceTree = "<group>"; };
                F6113E26126CE19B0057D0A7 /* WKUserContentURLPattern.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WKUserContentURLPattern.cpp; sourceTree = "<group>"; };
                F6113E27126CE19B0057D0A7 /* WKUserContentURLPattern.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKUserContentURLPattern.h; sourceTree = "<group>"; };
                                A19DD3BF1D07D16800AC823B /* _WKWebViewPrintFormatterInternal.h */,
                                1AD4C1911B39F33200ABC28E /* ApplicationStateTracker.h */,
                                1AD4C1901B39F33200ABC28E /* ApplicationStateTracker.mm */,
+                               F496A42F1F58A272004C1757 /* DragDropInteractionState.h */,
+                               F496A4301F58A272004C1757 /* DragDropInteractionState.mm */,
                                2DD45ADC1E5F8972006C355F /* InputViewUpdateDeferrer.h */,
                                2DD45ADD1E5F8972006C355F /* InputViewUpdateDeferrer.mm */,
                                0F0C365D18C110A500F607D7 /* LayerRepresentation.mm */,
                                1AD25E96167AB08100EA9BCD /* DownloadProxyMap.h in Headers */,
                                1AB7D61A1288B9D900CFD08C /* DownloadProxyMessages.h in Headers */,
                                C517388112DF8F4F00EE3F47 /* DragControllerAction.h in Headers */,
+                               F496A4311F58A272004C1757 /* DragDropInteractionState.h in Headers */,
                                BC8452A81162C80900CAB9B5 /* DrawingArea.h in Headers */,
                                0FB659231208B4DB0044816C /* DrawingAreaInfo.h in Headers */,
                                1A64229A12DD029200CAAE2C /* DrawingAreaMessages.h in Headers */,
                                1AB7D4CB1288AAA700CFD08C /* DownloadProxy.cpp in Sources */,
                                1AD25E95167AB08100EA9BCD /* DownloadProxyMap.cpp in Sources */,
                                1AB7D6191288B9D900CFD08C /* DownloadProxyMessageReceiver.cpp in Sources */,
+                               F496A4321F58A272004C1757 /* DragDropInteractionState.mm in Sources */,
                                BC8452A71162C80900CAB9B5 /* DrawingArea.cpp in Sources */,
                                1A64229912DD029200CAAE2C /* DrawingAreaMessageReceiver.cpp in Sources */,
                                BC2652161182608100243E12 /* DrawingAreaProxy.cpp in Sources */,
index d3f38f2c0616f7e16e70e22a7fe05306ba1bef35..51bf4cc079e58eb6437556cad19322fe6c3175ab 100644 (file)
@@ -1,3 +1,28 @@
+2017-09-04  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS DnD] Refactor drag and drop logic in WKContentView in preparation for supporting multiple drag items in a drag session
+        https://bugs.webkit.org/show_bug.cgi?id=176264
+        <rdar://problem/31144674>
+
+        Reviewed by Darin Adler.
+
+        Adds two new iOS drag and drop tests to check that the clientX and clientY attributes of mouse events propagated
+        to the page during drop are correct. Each test drags from an image element and drops into three custom-drop-
+        handling elements; `dragenter`, `dragover`, and `drop` event listeners on the body then use the clientX and
+        clientY event attributes to hit-test for drop target elements. The first test is suffixed with "-Basic"; the
+        second test, suffixed with "-WithScrollOffset", makes the document scrollable to check that clientY is correct
+        when scrolled.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/drop-targets.html: Added.
+        * TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
+        (TestWebKitAPI::testDragAndDropOntoTargetElements):
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/cocoa/TestWKWebView.mm:
+        (-[TestWKWebView stringByEvaluatingJavaScript:]):
+
+        Log a warning message when an API test fails due to JavaScript evaluation in TestWKWebView.
+
 2017-09-03  Filip Pizlo  <fpizlo@apple.com>
 
         WSL IntLiteral should have variable type so that it can unify with things like uint
index 3a7fe1554603c0f2020da045c0af59ff3426f960..260caa2fbe7589f9480a5082518f14907af27fb1 100644 (file)
                ECA680CE1E68CC0900731D20 /* StringUtilities.mm in Sources */ = {isa = PBXBuildFile; fileRef = ECA680CD1E68CC0900731D20 /* StringUtilities.mm */; };
                F407FE391F1D0DFC0017CF25 /* enormous.svg in Copy Resources */ = {isa = PBXBuildFile; fileRef = F407FE381F1D0DE60017CF25 /* enormous.svg */; };
                F415086D1DA040C50044BE9B /* play-audio-on-click.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F415086C1DA040C10044BE9B /* play-audio-on-click.html */; };
+               F4194AD11F5A320100ADD83F /* drop-targets.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4194AD01F5A2EA500ADD83F /* drop-targets.html */; };
                F41AB99F1EF4696B0083FA08 /* autofocus-contenteditable.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F41AB9981EF4692C0083FA08 /* autofocus-contenteditable.html */; };
                F41AB9A01EF4696B0083FA08 /* background-image-link-and-input.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F41AB9971EF4692C0083FA08 /* background-image-link-and-input.html */; };
                F41AB9A11EF4696B0083FA08 /* contenteditable-and-textarea.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F41AB99C1EF4692C0083FA08 /* contenteditable-and-textarea.html */; };
                                5714ECBB1CA8BFE400051AC8 /* DownloadRequestOriginalURLFrame.html in Copy Resources */,
                                F4A32EC41F05F3850047C544 /* dragstart-change-selection-offscreen.html in Copy Resources */,
                                F4D5E4E81F0C5D38008C1A49 /* dragstart-clear-selection.html in Copy Resources */,
+                               F4194AD11F5A320100ADD83F /* drop-targets.html in Copy Resources */,
                                A155022C1E050D0300A24C57 /* duplicate-completion-handler-calls.html in Copy Resources */,
                                9984FACE1CFFB090008D198C /* editable-body.html in Copy Resources */,
                                F44D06451F395C26001A0E29 /* editor-state-test-harness.html in Copy Resources */,
                F3FC3EE213678B7300126A65 /* libgtest.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgtest.a; sourceTree = BUILT_PRODUCTS_DIR; };
                F407FE381F1D0DE60017CF25 /* enormous.svg */ = {isa = PBXFileReference; lastKnownFileType = text; path = enormous.svg; sourceTree = "<group>"; };
                F415086C1DA040C10044BE9B /* play-audio-on-click.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "play-audio-on-click.html"; sourceTree = "<group>"; };
+               F4194AD01F5A2EA500ADD83F /* drop-targets.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "drop-targets.html"; sourceTree = "<group>"; };
                F41AB9931EF4692C0083FA08 /* image-and-textarea.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "image-and-textarea.html"; sourceTree = "<group>"; };
                F41AB9941EF4692C0083FA08 /* prevent-operation.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "prevent-operation.html"; sourceTree = "<group>"; };
                F41AB9951EF4692C0083FA08 /* textarea-to-input.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "textarea-to-input.html"; sourceTree = "<group>"; };
                                5714ECBA1CA8BFD100051AC8 /* DownloadRequestOriginalURLFrame.html */,
                                F4A32EC31F05F3780047C544 /* dragstart-change-selection-offscreen.html */,
                                F4D5E4E71F0C5D27008C1A49 /* dragstart-clear-selection.html */,
+                               F4194AD01F5A2EA500ADD83F /* drop-targets.html */,
                                A155022B1E050BC500A24C57 /* duplicate-completion-handler-calls.html */,
                                9984FACD1CFFB038008D198C /* editable-body.html */,
                                F44D06441F395C0D001A0E29 /* editor-state-test-harness.html */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/drop-targets.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/drop-targets.html
new file mode 100644 (file)
index 0000000..e82b294
--- /dev/null
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<html>
+<meta name="viewport" content="width=device-width">
+<head>
+<style>
+    body {
+        width: 100%;
+        height: 100%;
+        margin: 0;
+    }
+
+    img {
+        width: 100px;
+        height: 100px;
+    }
+
+    #target1 {
+        left: 0;
+        top: 200px;
+    }
+
+    #target2 {
+        left: 200px;
+        top: 0;
+    }
+
+    #target3 {
+        left: 200px;
+        top: 200px;
+    }
+
+    .target {
+        position: absolute;
+        background-color: red;
+        border-radius: 50%;
+        width: 100px;
+        height: 100px;
+        color: white;
+        text-align: center;
+        line-height: 90px;
+    }
+
+    #description {
+        padding-top: 250px;
+        display: block;
+    }
+
+    #container {
+        position: relative;
+    }
+</style>
+</head>
+
+<body>
+    <div id="container">
+    <img src="apple.gif"></img>
+    <div id="target1" class="target"></div>
+    <div id="target2" class="target"></div>
+    <div id="target3" class="target"></div>
+    <code id="description">
+        <div>To test this manually, drag the image onto each of the three red circles.</div>
+        <div>Each red circle should turn green upon dragging over it.</div>
+        <div>Upon drop, the text "PASS" should appear in each target.</div>
+    </code>
+    </div>
+</body>
+
+<script>
+    function elementAtMouseEvent(e) {
+        if (window.usePageCoordinates)
+            return document.elementFromPoint(e.pageX - pageXOffset, e.pageY - pageYOffset);
+        return document.elementFromPoint(e.clientX, e.clientY);
+    }
+
+    function targetAtEvent(e) {
+        let target = elementAtMouseEvent(e);
+        return target && target.classList.contains("target") ? target : null;
+    }
+
+    function resetTestState() {
+        target1.style.backgroundColor = "";
+        target2.style.backgroundColor = "";
+        target3.style.backgroundColor = "";
+        target1.innerHTML = "";
+        target2.innerHTML = "";
+        target3.innerHTML = "";
+    }
+
+    document.body.addEventListener("dragstart", resetTestState);
+    document.body.addEventListener("dragenter", event => {
+        let target = targetAtEvent(event);
+        if (target)
+            target.style.backgroundColor = "green";
+        event.preventDefault();
+    });
+
+    document.body.addEventListener("dragover", event => {
+        let target = targetAtEvent(event);
+        if (target)
+            target.style.backgroundColor = "green";
+        event.preventDefault();
+    });
+
+    document.body.addEventListener("drop", event => {
+        let target = targetAtEvent(event);
+        if (target)
+            target.innerHTML = `<code>PASS</code>`;
+        event.preventDefault();
+    });
+</script>
+</html>
index 9ab2a60b409d8cf6a7842defc8f0f8a3aaee0103..3f73bf0664788cad7043a16fc577e303129981a4 100644 (file)
@@ -1076,6 +1076,62 @@ TEST(DataInteractionTests, CancelledLiftDoesNotCauseSubsequentDragsToFail)
     checkStringArraysAreEqual(@[@"dragstart", @"dragend"], [outputText componentsSeparatedByString:@" "]);
 }
 
+static void testDragAndDropOntoTargetElements(TestWKWebView *webView)
+{
+    auto simulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView]);
+    [simulator runFrom:CGPointMake(50, 50) to:CGPointMake(50, 250)];
+    EXPECT_WK_STREQ("rgb(0, 128, 0)", [webView stringByEvaluatingJavaScript:@"getComputedStyle(target1).backgroundColor"]);
+    EXPECT_WK_STREQ("PASS", [webView stringByEvaluatingJavaScript:@"target1.textContent"]);
+
+    [simulator runFrom:CGPointMake(50, 50) to:CGPointMake(250, 50)];
+    EXPECT_WK_STREQ("rgb(0, 128, 0)", [webView stringByEvaluatingJavaScript:@"getComputedStyle(target2).backgroundColor"]);
+    EXPECT_WK_STREQ("PASS", [webView stringByEvaluatingJavaScript:@"target2.textContent"]);
+
+    [simulator runFrom:CGPointMake(50, 50) to:CGPointMake(250, 250)];
+    EXPECT_WK_STREQ("rgb(0, 128, 0)", [webView stringByEvaluatingJavaScript:@"getComputedStyle(target3).backgroundColor"]);
+    EXPECT_WK_STREQ("PASS", [webView stringByEvaluatingJavaScript:@"target3.textContent"]);
+}
+
+TEST(DataInteractionTests, DragEventClientCoordinatesBasic)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"drop-targets"];
+
+    testDragAndDropOntoTargetElements(webView.get());
+}
+
+TEST(DataInteractionTests, DragEventClientCoordinatesWithScrollOffset)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"drop-targets"];
+    [webView stringByEvaluatingJavaScript:@"document.body.style.margin = '1000px 0'"];
+    [webView stringByEvaluatingJavaScript:@"document.scrollingElement.scrollTop = 1000"];
+    [webView waitForNextPresentationUpdate];
+
+    testDragAndDropOntoTargetElements(webView.get());
+}
+
+TEST(DataInteractionTests, DragEventPageCoordinatesBasic)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"drop-targets"];
+    [webView stringByEvaluatingJavaScript:@"window.usePageCoordinates = true"];
+
+    testDragAndDropOntoTargetElements(webView.get());
+}
+
+TEST(DataInteractionTests, DragEventPageCoordinatesWithScrollOffset)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView synchronouslyLoadTestPageNamed:@"drop-targets"];
+    [webView stringByEvaluatingJavaScript:@"document.body.style.margin = '1000px 0'"];
+    [webView stringByEvaluatingJavaScript:@"document.scrollingElement.scrollTop = 1000"];
+    [webView stringByEvaluatingJavaScript:@"window.usePageCoordinates = true"];
+    [webView waitForNextPresentationUpdate];
+
+    testDragAndDropOntoTargetElements(webView.get());
+}
+
 TEST(DataInteractionTests, DoNotCrashWhenSelectionIsClearedInDragStart)
 {
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
index b756ca185793f7d3fc251339cba2d5c774ecefd5..339dc3b6333552b6a25b3ca4b01112cdcb012e66 100644 (file)
@@ -246,6 +246,8 @@ NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
         evalResult = [[NSString alloc] initWithFormat:@"%@", result];
         isWaitingForJavaScript = true;
         EXPECT_TRUE(!error);
+        if (error)
+            NSLog(@"Encountered error: %@ while evaluating script: %@", error, script);
     }];
 
     TestWebKitAPI::Util::run(&isWaitingForJavaScript);