[iOS DnD] [WK2] Dragged content flashes after the preview sets down when cancelling...
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 28 Jun 2017 19:35:39 +0000 (19:35 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 28 Jun 2017 19:35:39 +0000 (19:35 +0000)
https://bugs.webkit.org/show_bug.cgi?id=173927
<rdar://problem/33020792>

Reviewed by Tim Horton.

When a drag is cancelled, UIKit normally animates fly-back and set-down animations back-to-back. However, for
WebKit2, we need to call out to the web process in order to trigger a repaint of the dragged content at normal
opacity when the drag cancel animation completes. Thus, we can hook into UIKit SPI to split the cancellation
animation into two discrete phases: the fly-back and the set-down (the latter of which may be deferred using a
completion block).

To do this, we implement a new SPI hook, _dragInteraction:item:shouldDelaySetDownAnimationWithCompletion:, to
indicate that the set-down should be deferred, and call the completion block after the next layer tree commit.
Since -shouldDelaySetDownAnimationWithCompletion: is invoked after the UIDragAnimating completion block in
-willAnimateCancelWithAnimator:, this layer tree commit is guaranteed to come after we've told the web process
to repaint at full opacity.

This patch also accounts for additional changes in UIKit. Once -shouldDelaySetDownAnimationWithCompletion:
becomes available, -sessionDidEnd: will no longer be invoked after a cancelled drag lift if the user did not
begin moving. This means that our cleanup logic in -sessionDidEnd: will no longer be executed in this case,
leaving the view with an inconsistent dragging state. To fix this, we need to implement
-dragInteraction:willAnimateLiftWithAnimator:session: to perform drag session cleanup following a cancelled drag
lift, for which the drag animator's final position will be UIViewAnimatingPositionStart.

* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView dragInteraction:willAnimateLiftWithAnimator:session:]):
(-[WKContentView _dragInteraction:item:shouldDelaySetDownAnimationWithCompletion:]):

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

Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/ios/WKContentViewInteraction.mm

index 1dbf5db..22f8d34 100644 (file)
@@ -1,3 +1,34 @@
+2017-06-28  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS DnD] [WK2] Dragged content flashes after the preview sets down when cancelling a drag
+        https://bugs.webkit.org/show_bug.cgi?id=173927
+        <rdar://problem/33020792>
+
+        Reviewed by Tim Horton.
+
+        When a drag is cancelled, UIKit normally animates fly-back and set-down animations back-to-back. However, for
+        WebKit2, we need to call out to the web process in order to trigger a repaint of the dragged content at normal
+        opacity when the drag cancel animation completes. Thus, we can hook into UIKit SPI to split the cancellation
+        animation into two discrete phases: the fly-back and the set-down (the latter of which may be deferred using a
+        completion block).
+
+        To do this, we implement a new SPI hook, _dragInteraction:item:shouldDelaySetDownAnimationWithCompletion:, to
+        indicate that the set-down should be deferred, and call the completion block after the next layer tree commit.
+        Since -shouldDelaySetDownAnimationWithCompletion: is invoked after the UIDragAnimating completion block in
+        -willAnimateCancelWithAnimator:, this layer tree commit is guaranteed to come after we've told the web process
+        to repaint at full opacity.
+
+        This patch also accounts for additional changes in UIKit. Once -shouldDelaySetDownAnimationWithCompletion:
+        becomes available, -sessionDidEnd: will no longer be invoked after a cancelled drag lift if the user did not
+        begin moving. This means that our cleanup logic in -sessionDidEnd: will no longer be executed in this case,
+        leaving the view with an inconsistent dragging state. To fix this, we need to implement
+        -dragInteraction:willAnimateLiftWithAnimator:session: to perform drag session cleanup following a cancelled drag
+        lift, for which the drag animator's final position will be UIViewAnimatingPositionStart.
+
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView dragInteraction:willAnimateLiftWithAnimator:session:]):
+        (-[WKContentView _dragInteraction:item:shouldDelaySetDownAnimationWithCompletion:]):
+
 2017-06-28  Chris Dumez  <cdumez@apple.com>
 
         Avoid double thread dispatch in WebsiteDataStore::fetchDataForTopPrivatelyControlledDomains()
index 035b8cf..ca52fc5 100644 (file)
@@ -4603,6 +4603,21 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
     return self.dragPreviewForCurrentDataInteractionState.autorelease();
 }
 
+- (void)dragInteraction:(UIDragInteraction *)interaction willAnimateLiftWithAnimator:(id <UIDragAnimating>)animator session:(id <UIDragSession>)session
+{
+    auto adjustedOrigin = _dataInteractionState.adjustedOrigin;
+    RetainPtr<WKContentView> protectedSelf(self);
+    [animator addCompletion:[session, adjustedOrigin, 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);
+        }
+    }];
+}
+
 - (void)dragInteraction:(UIDragInteraction *)interaction sessionWillBegin:(id <UIDragSession>)session
 {
     RELEASE_LOG(DragAndDrop, "Drag session beginning: %p", session);
@@ -4643,6 +4658,14 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
     return self.dragPreviewForCurrentDataInteractionState.autorelease();
 }
 
+- (BOOL)_dragInteraction:(UIDragInteraction *)interaction item:(UIDragItem *)item shouldDelaySetDownAnimationWithCompletion:(void(^)(void))completion
+{
+    _page->callAfterNextPresentationUpdate([capturedBlock = makeBlockPtr(completion)] (CallbackBase::Error) {
+        capturedBlock();
+    });
+    return YES;
+}
+
 - (void)_api_dragInteraction:(UIDragInteraction *)interaction item:(UIDragItem *)item willAnimateCancelWithAnimator:(id <UIDragAnimating>)animator
 {
     [animator addCompletion:[page = _page] (UIViewAnimatingPosition finalPosition) {