Web Content process gets stuck suspended after navigating away from a system preview
[WebKit-https.git] / Tools / TestWebKitAPI / ios / DragAndDropSimulatorIOS.mm
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "DragAndDropSimulator.h"
28
29 #if ENABLE(DRAG_SUPPORT) && PLATFORM(IOS_FAMILY)
30
31 #import "InstanceMethodSwizzler.h"
32 #import "PlatformUtilities.h"
33 #import "UIKitSPI.h"
34
35 #import <UIKit/UIDragInteraction.h>
36 #import <UIKit/UIDragItem.h>
37 #import <UIKit/UIDropInteraction.h>
38 #import <UIKit/UIInteraction.h>
39 #import <WebKit/WKWebViewPrivate.h>
40 #import <WebKit/_WKFocusedElementInfo.h>
41 #import <WebKit/_WKFormInputSession.h>
42 #import <wtf/RetainPtr.h>
43 #import <wtf/SoftLinking.h>
44
45 SOFT_LINK_FRAMEWORK(UIKit)
46 SOFT_LINK(UIKit, UIApplicationInstantiateSingleton, void, (Class singletonClass), (singletonClass))
47
48 using namespace TestWebKitAPI;
49
50 @implementation WKWebView (DragAndDropTesting)
51
52 - (UIView *)_dragDropInteractionView
53 {
54     return [self valueForKey:@"_currentContentView"];
55 }
56
57 - (id <UIDropInteractionDelegate>)dropInteractionDelegate
58 {
59     return (id <UIDropInteractionDelegate>)self._dragDropInteractionView;
60 }
61
62 - (id <UIDragInteractionDelegate>)dragInteractionDelegate
63 {
64     return (id <UIDragInteractionDelegate>)self._dragDropInteractionView;
65 }
66
67 - (UIDropInteraction *)dropInteraction
68 {
69     UIView *interactionView = self._dragDropInteractionView;
70     for (id <UIInteraction> interaction in interactionView.interactions) {
71         if ([interaction isKindOfClass:[UIDropInteraction class]])
72             return (UIDropInteraction *)interaction;
73     }
74     return nil;
75 }
76
77 - (UIDragInteraction *)dragInteraction
78 {
79     UIView *interactionView = self._dragDropInteractionView;
80     for (id <UIInteraction> interaction in interactionView.interactions) {
81         if ([interaction isKindOfClass:[UIDragInteraction class]])
82             return (UIDragInteraction *)interaction;
83     }
84     return nil;
85 }
86
87 @end
88
89 @implementation MockDragDropSession
90
91 - (instancetype)initWithItems:(NSArray <UIDragItem *>*)items location:(CGPoint)locationInWindow window:(UIWindow *)window allowMove:(BOOL)allowMove
92 {
93     if (self = [super init]) {
94         _mockItems = items;
95         _mockLocationInWindow = locationInWindow;
96         _window = window;
97         _allowMove = allowMove;
98     }
99     return self;
100 }
101
102 - (BOOL)allowsMoveOperation
103 {
104     return _allowMove;
105 }
106
107 - (BOOL)isRestrictedToDraggingApplication
108 {
109     return NO;
110 }
111
112 - (BOOL)hasItemsConformingToTypeIdentifiers:(NSArray<NSString *> *)typeIdentifiers
113 {
114     for (NSString *typeIdentifier in typeIdentifiers) {
115         BOOL hasItemConformingToType = NO;
116         for (UIDragItem *item in self.items)
117             hasItemConformingToType |= [[item.itemProvider registeredTypeIdentifiers] containsObject:typeIdentifier];
118         if (!hasItemConformingToType)
119             return NO;
120     }
121     return YES;
122 }
123
124 - (BOOL)canLoadObjectsOfClass:(Class<NSItemProviderReading>)aClass
125 {
126     for (UIDragItem *item in self.items) {
127         if ([item.itemProvider canLoadObjectOfClass:aClass])
128             return YES;
129     }
130     return NO;
131 }
132
133 - (BOOL)canLoadObjectsOfClasses:(NSArray<Class<NSItemProviderReading>> *)classes
134 {
135     for (Class<NSItemProviderReading> aClass in classes) {
136         BOOL canLoad = NO;
137         for (UIDragItem *item in self.items)
138             canLoad |= [item.itemProvider canLoadObjectOfClass:aClass];
139         if (!canLoad)
140             return NO;
141     }
142     return YES;
143 }
144
145 - (NSArray<UIDragItem *> *)items
146 {
147     return _mockItems.get();
148 }
149
150 - (void)setItems:(NSArray<UIDragItem *> *)items
151 {
152     _mockItems = items;
153 }
154
155 - (void)addItems:(NSArray<UIDragItem *> *)items
156 {
157     if (![items count])
158         return;
159
160     if (![_mockItems count])
161         _mockItems = items;
162     else
163         _mockItems = [_mockItems arrayByAddingObjectsFromArray:items];
164 }
165
166 - (CGPoint)locationInView:(UIView *)view
167 {
168     return [_window convertPoint:_mockLocationInWindow toView:view];
169 }
170
171 @end
172
173 @implementation MockDropSession
174
175 - (instancetype)initWithProviders:(NSArray<NSItemProvider *> *)providers location:(CGPoint)locationInWindow window:(UIWindow *)window allowMove:(BOOL)allowMove
176 {
177     auto items = adoptNS([[NSMutableArray alloc] init]);
178     for (NSItemProvider *itemProvider in providers)
179         [items addObject:[[[UIDragItem alloc] initWithItemProvider:itemProvider] autorelease]];
180
181     return [super initWithItems:items.get() location:locationInWindow window:window allowMove:allowMove];
182 }
183
184 - (BOOL)isLocal
185 {
186     return YES;
187 }
188
189 - (NSProgress *)progress
190 {
191     return [NSProgress discreteProgressWithTotalUnitCount:100];
192 }
193
194 - (void)setProgressIndicatorStyle:(UIDropSessionProgressIndicatorStyle)progressIndicatorStyle
195 {
196 }
197
198 - (UIDropSessionProgressIndicatorStyle)progressIndicatorStyle
199 {
200     return UIDropSessionProgressIndicatorStyleNone;
201 }
202
203 - (NSUInteger)operationMask
204 {
205     return 0;
206 }
207
208 - (id <UIDragSession>)localDragSession
209 {
210     return nil;
211 }
212
213 - (BOOL)hasItemsConformingToTypeIdentifier:(NSString *)typeIdentifier
214 {
215     ASSERT_NOT_REACHED();
216     return NO;
217 }
218
219 - (BOOL)canCreateItemsOfClass:(Class<NSItemProviderReading>)aClass
220 {
221     ASSERT_NOT_REACHED();
222     return NO;
223 }
224
225 - (NSProgress *)loadObjectsOfClass:(Class<NSItemProviderReading>)aClass completion:(void(^)(NSArray<__kindof id <NSItemProviderReading>> *objects))completion
226 {
227     ASSERT_NOT_REACHED();
228     return nil;
229 }
230
231 @end
232
233 @implementation MockDragSession {
234     RetainPtr<id> _localContext;
235 }
236
237 - (instancetype)initWithWindow:(UIWindow *)window allowMove:(BOOL)allowMove
238 {
239     return [super initWithItems:@[ ] location:CGPointZero window:window allowMove:allowMove];
240 }
241
242 - (NSUInteger)localOperationMask
243 {
244     ASSERT_NOT_REACHED();
245     return 0;
246 }
247
248 - (NSUInteger)externalOperationMask
249 {
250     ASSERT_NOT_REACHED();
251     return 0;
252 }
253
254 - (id)session
255 {
256     return nil;
257 }
258
259 - (id)localContext
260 {
261     return _localContext.get();
262 }
263
264 - (void)setLocalContext:(id)localContext
265 {
266     _localContext = localContext;
267 }
268
269 @end
270
271 static double progressIncrementStep = 0.033;
272 static double progressTimeStep = 0.016;
273 static NSString *TestWebKitAPISimulateCancelAllTouchesNotificationName = @"TestWebKitAPISimulateCancelAllTouchesNotificationName";
274
275 static NSArray *dragAndDropEventNames()
276 {
277     static NSArray *eventNames = nil;
278     static dispatch_once_t onceToken;
279     dispatch_once(&onceToken, ^() {
280         eventNames = @[ @"dragenter", @"dragover", @"drop", @"dragleave", @"dragstart" ];
281     });
282     return eventNames;
283 }
284
285 @interface DragAndDropSimulatorApplication : UIApplication
286 @end
287
288 @implementation DragAndDropSimulatorApplication
289
290 IGNORE_WARNINGS_BEGIN("deprecated-implementations")
291 - (void)_cancelAllTouches
292 {
293     [[NSNotificationCenter defaultCenter] postNotificationName:TestWebKitAPISimulateCancelAllTouchesNotificationName object:nil];
294 }
295 IGNORE_WARNINGS_END
296
297 @end
298
299 @implementation DragAndDropSimulator {
300     RetainPtr<TestWKWebView> _webView;
301     RetainPtr<MockDragSession> _dragSession;
302     RetainPtr<MockDropSession> _dropSession;
303     RetainPtr<NSMutableArray> _observedEventNames;
304     RetainPtr<NSArray> _externalItemProviders;
305     RetainPtr<NSArray> _sourceItemProviders;
306     CGRect _finalSelectionStartRect;
307     CGPoint _startLocation;
308     CGPoint _endLocation;
309     CGRect _lastKnownDragCaretRect;
310
311     RetainPtr<NSMutableDictionary<NSNumber *, NSValue *>>_remainingAdditionalItemRequestLocationsByProgress;
312     RetainPtr<NSMutableArray<NSValue *>>_queuedAdditionalItemRequestLocations;
313     RetainPtr<NSMutableArray> _liftPreviews;
314     RetainPtr<NSMutableArray<UITargetedDragPreview *>> _cancellationPreviews;
315     RetainPtr<NSMutableArray> _dropPreviews;
316     RetainPtr<NSMutableArray> _delayedDropPreviews;
317
318     RetainPtr<NSMutableArray<_WKAttachment *>> _insertedAttachments;
319     RetainPtr<NSMutableArray<_WKAttachment *>> _removedAttachments;
320
321     bool _hasStartedInputSession;
322     double _currentProgress;
323     bool _isDoneWithCurrentRun;
324     bool _isDoneWaitingForDelayedDropPreviews;
325     DragAndDropPhase _phase;
326
327     RetainPtr<UIDropProposal> _lastKnownDropProposal;
328
329     BlockPtr<BOOL(_WKActivatedElementInfo *)> _showCustomActionSheetBlock;
330     BlockPtr<NSArray *(NSItemProvider *, NSArray *, NSDictionary *)> _convertItemProvidersBlock;
331     BlockPtr<NSArray *(id <UIDropSession>)> _overridePerformDropBlock;
332     BlockPtr<UIDropOperation(UIDropOperation, id)> _overrideDragUpdateBlock;
333     BlockPtr<void(BOOL, NSArray *)> _dropCompletionBlock;
334     BlockPtr<void()> _sessionWillBeginBlock;
335 }
336
337 - (instancetype)initWithWebViewFrame:(CGRect)frame
338 {
339     return [self initWithWebViewFrame:frame configuration:nil];
340 }
341
342 - (instancetype)initWithWebViewFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
343 {
344     if (configuration)
345         return [self initWithWebView:[[[TestWKWebView alloc] initWithFrame:frame configuration:configuration] autorelease]];
346
347     return [self initWithWebView:[[[TestWKWebView alloc] initWithFrame:frame] autorelease]];
348 }
349
350 - (instancetype)initWithWebView:(TestWKWebView *)webView
351 {
352     if (self = [super init]) {
353         _webView = webView;
354         _shouldEnsureUIApplication = NO;
355         _shouldBecomeFirstResponder = YES;
356         _shouldAllowMoveOperation = YES;
357         [_webView setUIDelegate:self];
358         [_webView _setInputDelegate:self];
359         self.dragDestinationAction = WKDragDestinationActionAny & ~WKDragDestinationActionLoad;
360     }
361     return self;
362 }
363
364 - (void)dealloc
365 {
366     if ([_webView UIDelegate] == self)
367         [_webView setUIDelegate:nil];
368
369     if ([_webView _inputDelegate] == self)
370         [_webView _setInputDelegate:nil];
371
372     [super dealloc];
373 }
374
375 - (void)_resetSimulatedState
376 {
377     _phase = DragAndDropPhaseBeginning;
378     _currentProgress = 0;
379     _isDoneWithCurrentRun = false;
380     _isDoneWaitingForDelayedDropPreviews = true;
381     _observedEventNames = adoptNS([[NSMutableArray alloc] init]);
382     _insertedAttachments = adoptNS([[NSMutableArray alloc] init]);
383     _removedAttachments = adoptNS([[NSMutableArray alloc] init]);
384     _finalSelectionStartRect = CGRectNull;
385     _dragSession = nil;
386     _dropSession = nil;
387     _lastKnownDropProposal = nil;
388     _lastKnownDragCaretRect = CGRectZero;
389     _remainingAdditionalItemRequestLocationsByProgress = nil;
390     _queuedAdditionalItemRequestLocations = adoptNS([[NSMutableArray alloc] init]);
391     _liftPreviews = adoptNS([[NSMutableArray alloc] init]);
392     _dropPreviews = adoptNS([[NSMutableArray alloc] init]);
393     _cancellationPreviews = adoptNS([[NSMutableArray alloc] init]);
394     _delayedDropPreviews = adoptNS([[NSMutableArray alloc] init]);
395     _hasStartedInputSession = false;
396 }
397
398 - (NSArray *)observedEventNames
399 {
400     return _observedEventNames.get();
401 }
402
403 - (UIDropProposal *)lastKnownDropProposal
404 {
405     return _lastKnownDropProposal.get();
406 }
407
408 - (void)simulateAllTouchesCanceled:(NSNotification *)notification
409 {
410     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_advanceProgress) object:nil];
411     _phase = DragAndDropPhaseCancelled;
412     _currentProgress = 1;
413     _isDoneWithCurrentRun = true;
414     if (_dragSession)
415         [[_webView dragInteractionDelegate] dragInteraction:[_webView dragInteraction] session:_dragSession.get() didEndWithOperation:UIDropOperationCancel];
416 }
417
418 - (void)runFrom:(CGPoint)startLocation to:(CGPoint)endLocation
419 {
420     [self runFrom:startLocation to:endLocation additionalItemRequestLocations:nil];
421 }
422
423 - (void)runFrom:(CGPoint)startLocation to:(CGPoint)endLocation additionalItemRequestLocations:(ProgressToCGPointValueMap)additionalItemRequestLocations
424 {
425     NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
426     [defaultCenter addObserver:self selector:@selector(simulateAllTouchesCanceled:) name:TestWebKitAPISimulateCancelAllTouchesNotificationName object:nil];
427
428     if (_shouldEnsureUIApplication)
429         UIApplicationInstantiateSingleton([DragAndDropSimulatorApplication class]);
430
431     if (_shouldBecomeFirstResponder)
432         [_webView becomeFirstResponder];
433
434     [self _resetSimulatedState];
435
436     if (additionalItemRequestLocations)
437         _remainingAdditionalItemRequestLocationsByProgress = adoptNS([additionalItemRequestLocations mutableCopy]);
438
439     for (NSString *eventName in dragAndDropEventNames()) {
440         [_webView performAfterReceivingMessage:eventName action:[strongSelf = retainPtr(self), name = retainPtr(eventName)] {
441             [strongSelf->_observedEventNames addObject:name.get()];
442         }];
443     }
444
445     _startLocation = startLocation;
446     _endLocation = endLocation;
447
448     if (self.externalItemProviders.count) {
449         _dropSession = adoptNS([[MockDropSession alloc] initWithProviders:self.externalItemProviders location:_startLocation window:[_webView window] allowMove:self.shouldAllowMoveOperation]);
450         _phase = DragAndDropPhaseBegan;
451         [self _advanceProgress];
452     } else {
453         _dragSession = adoptNS([[MockDragSession alloc] initWithWindow:[_webView window] allowMove:self.shouldAllowMoveOperation]);
454         [_dragSession setMockLocationInWindow:_startLocation];
455         [(id <UIDragInteractionDelegate_ForWebKitOnly>)[_webView dragInteractionDelegate] _dragInteraction:[_webView dragInteraction] prepareForSession:_dragSession.get() completion:[strongSelf = retainPtr(self)] {
456             if (strongSelf->_phase == DragAndDropPhaseCancelled)
457                 return;
458
459             strongSelf->_phase = DragAndDropPhaseBeginning;
460             [strongSelf _advanceProgress];
461         }];
462     }
463
464     Util::run(&_isDoneWithCurrentRun);
465     Util::run(&_isDoneWaitingForDelayedDropPreviews);
466     [_webView clearMessageHandlers:dragAndDropEventNames()];
467     [_webView waitForNextPresentationUpdate];
468
469     auto contentView = [_webView textInputContentView];
470     _finalSelectionStartRect = [contentView caretRectForPosition:contentView.selectedTextRange.start];
471
472     [defaultCenter removeObserver:self];
473 }
474
475 - (void)_concludeDropAndPerformOperationIfNecessary
476 {
477     _lastKnownDragCaretRect = [_webView _dragCaretRect];
478     auto operation = [_lastKnownDropProposal operation];
479     if (operation != UIDropOperationCancel && operation != UIDropOperationForbidden) {
480         NSInteger dropPreviewIndex = 0;
481         __block NSUInteger numberOfPendingPreviews = [_dropSession items].count;
482         _isDoneWaitingForDelayedDropPreviews = !numberOfPendingPreviews;
483         for (UIDragItem *item in [_dropSession items]) {
484             auto defaultPreview = adoptNS([[UITargetedDragPreview alloc] initWithView:_webView.get()]);
485             id <UIDropInteractionDelegate_Staging_31075005> delegate = (id <UIDropInteractionDelegate_Staging_31075005>)[_webView dropInteractionDelegate];
486             UIDropInteraction *interaction = [_webView dropInteraction];
487             [_dropPreviews addObject:[delegate dropInteraction:interaction previewForDroppingItem:item withDefault:defaultPreview.get()] ?: NSNull.null];
488             [_delayedDropPreviews addObject:NSNull.null];
489             [delegate _dropInteraction:interaction delayedPreviewProviderForDroppingItem:item previewProvider:^(UITargetedDragPreview *preview) {
490                 if (preview)
491                     [_delayedDropPreviews setObject:preview atIndexedSubscript:dropPreviewIndex];
492
493                 if (!--numberOfPendingPreviews)
494                     _isDoneWaitingForDelayedDropPreviews = true;
495             }];
496             ++dropPreviewIndex;
497         }
498         [[_webView dropInteractionDelegate] dropInteraction:[_webView dropInteraction] performDrop:_dropSession.get()];
499         _phase = DragAndDropPhasePerformingDrop;
500     } else {
501         _isDoneWithCurrentRun = true;
502         _phase = DragAndDropPhaseCancelled;
503         [[_dropSession items] enumerateObjectsUsingBlock:^(UIDragItem *item, NSUInteger index, BOOL *) {
504             UITargetedDragPreview *defaultPreview = nil;
505             if ([_liftPreviews count] && [[_liftPreviews objectAtIndex:index] isEqual:NSNull.null])
506                 defaultPreview = [_liftPreviews objectAtIndex:index];
507
508             UITargetedDragPreview *preview = [[_webView dragInteractionDelegate] dragInteraction:[_webView dragInteraction] previewForCancellingItem:item withDefault:defaultPreview];
509             if (preview)
510                 [_cancellationPreviews addObject:preview];
511         }];
512         [[_webView dropInteractionDelegate] dropInteraction:[_webView dropInteraction] concludeDrop:_dropSession.get()];
513     }
514
515     [[_webView dropInteractionDelegate] dropInteraction:[_webView dropInteraction] sessionDidEnd:_dropSession.get()];
516
517     if (_dragSession) {
518         auto delegate = [_webView dragInteractionDelegate];
519         [delegate dragInteraction:[_webView dragInteraction] session:_dragSession.get() didEndWithOperation:operation];
520         if ([delegate respondsToSelector:@selector(_clearToken:)])
521             [(id <UITextInputMultiDocument>)delegate _clearToken:nil];
522         [_webView becomeFirstResponder];
523     }
524 }
525
526 - (void)_enqueuePendingAdditionalItemRequestLocations
527 {
528     NSMutableArray *progressValuesToRemove = [NSMutableArray array];
529     for (NSNumber *progressValue in _remainingAdditionalItemRequestLocationsByProgress.get()) {
530         double progress = progressValue.doubleValue;
531         if (progress > _currentProgress)
532             continue;
533         [progressValuesToRemove addObject:progressValue];
534         [_queuedAdditionalItemRequestLocations addObject:[_remainingAdditionalItemRequestLocationsByProgress objectForKey:progressValue]];
535     }
536
537     for (NSNumber *progressToRemove in progressValuesToRemove)
538         [_remainingAdditionalItemRequestLocationsByProgress removeObjectForKey:progressToRemove];
539 }
540
541 - (BOOL)_sendQueuedAdditionalItemRequest
542 {
543     if (![_queuedAdditionalItemRequestLocations count])
544         return NO;
545
546     RetainPtr<NSValue> requestLocationValue = [_queuedAdditionalItemRequestLocations objectAtIndex:0];
547     [_queuedAdditionalItemRequestLocations removeObjectAtIndex:0];
548
549     auto requestLocation = [[_webView window] convertPoint:[requestLocationValue CGPointValue] toView:_webView.get()];
550     [(id <UIDragInteractionDelegate_ForWebKitOnly>)[_webView dragInteractionDelegate] _dragInteraction:[_webView dragInteraction] itemsForAddingToSession:_dragSession.get() withTouchAtPoint:requestLocation completion:[dragSession = _dragSession, dropSession = _dropSession] (NSArray *items) {
551         [dragSession addItems:items];
552         [dropSession addItems:items];
553     }];
554     return YES;
555 }
556
557 - (void)_advanceProgress
558 {
559     [self _enqueuePendingAdditionalItemRequestLocations];
560     if ([self _sendQueuedAdditionalItemRequest]) {
561         [self _scheduleAdvanceProgress];
562         return;
563     }
564
565     _lastKnownDragCaretRect = [_webView _dragCaretRect];
566     _currentProgress += progressIncrementStep;
567     CGPoint locationInWindow = self._currentLocation;
568     [_dragSession setMockLocationInWindow:locationInWindow];
569     [_dropSession setMockLocationInWindow:locationInWindow];
570
571     if (_currentProgress >= 1) {
572         _currentProgress = 1;
573         [self _concludeDropAndPerformOperationIfNecessary];
574         return;
575     }
576
577     switch (_phase) {
578     case DragAndDropPhaseBeginning: {
579         NSMutableArray<NSItemProvider *> *itemProviders = [NSMutableArray array];
580         NSArray *items = [[_webView dragInteractionDelegate] dragInteraction:[_webView dragInteraction] itemsForBeginningSession:_dragSession.get()];
581         if (!items.count) {
582             _phase = DragAndDropPhaseCancelled;
583             _currentProgress = 1;
584             _isDoneWithCurrentRun = true;
585             return;
586         }
587
588         for (UIDragItem *item in items) {
589             [itemProviders addObject:item.itemProvider];
590             UITargetedDragPreview *liftPreview = [[_webView dragInteractionDelegate] dragInteraction:[_webView dragInteraction] previewForLiftingItem:item session:_dragSession.get()];
591             EXPECT_TRUE(liftPreview || ![_webView window]);
592             [_liftPreviews addObject:liftPreview ?: NSNull.null];
593         }
594
595         _dropSession = adoptNS([[MockDropSession alloc] initWithProviders:itemProviders location:self._currentLocation window:[_webView window] allowMove:self.shouldAllowMoveOperation]);
596         [_dragSession setItems:items];
597         _sourceItemProviders = itemProviders;
598         if (self.showCustomActionSheetBlock) {
599             // Defer progress until the custom action sheet is dismissed.
600             auto startLocationInView = [[_webView window] convertPoint:_startLocation toView:_webView.get()];
601             [_webView _simulateLongPressActionAtLocation:startLocationInView];
602             return;
603         }
604
605         auto delegate = [_webView dragInteractionDelegate];
606         if ([delegate respondsToSelector:@selector(_preserveFocusWithToken:destructively:)])
607             [(id <UITextInputMultiDocument>)delegate _preserveFocusWithToken:nil destructively:NO];
608
609         [_webView resignFirstResponder];
610
611         [delegate dragInteraction:[_webView dragInteraction] sessionWillBegin:_dragSession.get()];
612
613         RetainPtr<WKWebView> retainedWebView = _webView;
614         dispatch_async(dispatch_get_main_queue(), ^() {
615             [retainedWebView resignFirstResponder];
616         });
617
618         _phase = DragAndDropPhaseBegan;
619         break;
620     }
621     case DragAndDropPhaseBegan:
622         [[_webView dropInteractionDelegate] dropInteraction:[_webView dropInteraction] sessionDidEnter:_dropSession.get()];
623         _phase = DragAndDropPhaseEntered;
624         break;
625     case DragAndDropPhaseEntered: {
626         _lastKnownDropProposal = [[_webView dropInteractionDelegate] dropInteraction:[_webView dropInteraction] sessionDidUpdate:_dropSession.get()];
627         if (![self shouldAllowMoveOperation] && [_lastKnownDropProposal operation] == UIDropOperationMove)
628             _lastKnownDropProposal = adoptNS([[UIDropProposal alloc] initWithDropOperation:UIDropOperationCancel]);
629         break;
630     }
631     default:
632         break;
633     }
634
635     [self _scheduleAdvanceProgress];
636 }
637
638 - (void)clearExternalDragInformation
639 {
640     _externalItemProviders = nil;
641 }
642
643 - (CGPoint)_currentLocation
644 {
645     CGFloat distanceX = _endLocation.x - _startLocation.x;
646     CGFloat distanceY = _endLocation.y - _startLocation.y;
647     return CGPointMake(_startLocation.x + _currentProgress * distanceX, _startLocation.y + _currentProgress * distanceY);
648 }
649
650 - (void)_scheduleAdvanceProgress
651 {
652     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_advanceProgress) object:nil];
653     [self performSelector:@selector(_advanceProgress) withObject:nil afterDelay:progressTimeStep];
654 }
655
656 - (NSArray *)sourceItemProviders
657 {
658     return _sourceItemProviders.get();
659 }
660
661 - (NSArray *)externalItemProviders
662 {
663     return _externalItemProviders.get();
664 }
665
666 - (void)setExternalItemProviders:(NSArray *)externalItemProviders
667 {
668     _externalItemProviders = adoptNS([externalItemProviders copy]);
669 }
670
671 - (DragAndDropPhase)phase
672 {
673     return _phase;
674 }
675
676 - (NSArray *)liftPreviews
677 {
678     return _liftPreviews.get();
679 }
680
681 - (NSArray<UITargetedDragPreview *> *)cancellationPreviews
682 {
683     return _cancellationPreviews.get();
684 }
685
686 - (NSArray<UITargetedDragPreview *> *)dropPreviews
687 {
688     return _dropPreviews.get();
689 }
690
691 - (NSArray<UITargetedDragPreview *> *)delayedDropPreviews
692 {
693     return _delayedDropPreviews.get();
694 }
695
696 - (CGRect)lastKnownDragCaretRect
697 {
698     return _lastKnownDragCaretRect;
699 }
700
701 - (void)ensureInputSession
702 {
703     Util::run(&_hasStartedInputSession);
704 }
705
706 - (NSArray<_WKAttachment *> *)insertedAttachments
707 {
708     return _insertedAttachments.get();
709 }
710
711 - (NSArray<_WKAttachment *> *)removedAttachments
712 {
713     return _removedAttachments.get();
714 }
715
716 - (void)endDataTransfer
717 {
718     [[_webView dragInteractionDelegate] dragInteraction:[_webView dragInteraction] sessionDidTransferItems:_dragSession.get()];
719 }
720
721 - (TestWKWebView *)webView
722 {
723     return _webView.get();
724 }
725
726 - (void)setShowCustomActionSheetBlock:(BOOL(^)(_WKActivatedElementInfo *))showCustomActionSheetBlock
727 {
728     _showCustomActionSheetBlock = showCustomActionSheetBlock;
729 }
730
731 - (BOOL(^)(_WKActivatedElementInfo *))showCustomActionSheetBlock
732 {
733     return _showCustomActionSheetBlock.get();
734 }
735
736 - (void)setConvertItemProvidersBlock:(NSArray *(^)(NSItemProvider *, NSArray *, NSDictionary *))convertItemProvidersBlock
737 {
738     _convertItemProvidersBlock = convertItemProvidersBlock;
739 }
740
741 - (NSArray *(^)(NSItemProvider *, NSArray *, NSDictionary *))convertItemProvidersBlock
742 {
743     return _convertItemProvidersBlock.get();
744 }
745
746 - (void)setOverridePerformDropBlock:(NSArray *(^)(id <UIDropSession>))overridePerformDropBlock
747 {
748     _overridePerformDropBlock = overridePerformDropBlock;
749 }
750
751 - (NSArray *(^)(id <UIDropSession>))overridePerformDropBlock
752 {
753     return _overridePerformDropBlock.get();
754 }
755
756 - (void)setOverrideDragUpdateBlock:(UIDropOperation(^)(UIDropOperation, id <UIDropSession>))overrideDragUpdateBlock
757 {
758     _overrideDragUpdateBlock = overrideDragUpdateBlock;
759 }
760
761 - (UIDropOperation(^)(UIDropOperation, id <UIDropSession>))overrideDragUpdateBlock
762 {
763     return _overrideDragUpdateBlock.get();
764 }
765
766 - (void)setDropCompletionBlock:(void(^)(BOOL, NSArray *))dropCompletionBlock
767 {
768     _dropCompletionBlock = dropCompletionBlock;
769 }
770
771 - (void(^)(BOOL, NSArray *))dropCompletionBlock
772 {
773     return _dropCompletionBlock.get();
774 }
775
776 - (void)setSessionWillBeginBlock:(dispatch_block_t)block
777 {
778     _sessionWillBeginBlock = block;
779 }
780
781 - (dispatch_block_t)sessionWillBeginBlock
782 {
783     return _sessionWillBeginBlock.get();
784 }
785
786 #pragma mark - WKUIDelegatePrivate
787
788 - (void)_webView:(WKWebView *)webView dataInteraction:(UIDragInteraction *)interaction sessionWillBegin:(id <UIDragSession>)session
789 {
790     if (_sessionWillBeginBlock)
791         _sessionWillBeginBlock();
792 }
793
794 - (void)_webView:(WKWebView *)webView dataInteractionOperationWasHandled:(BOOL)handled forSession:(id)session itemProviders:(NSArray<NSItemProvider *> *)itemProviders
795 {
796     if (self.dropCompletionBlock)
797         self.dropCompletionBlock(handled, itemProviders);
798
799     [_webView _doAfterReceivingEditDragSnapshotForTesting:^{
800         [[_webView dropInteractionDelegate] dropInteraction:[_webView dropInteraction] concludeDrop:_dropSession.get()];
801         _isDoneWithCurrentRun = true;
802     }];
803 }
804
805 - (UIDropProposal *)_webView:(WKWebView *)webView willUpdateDropProposalToProposal:(UIDropProposal *)proposal forSession:(id <UIDropSession>)session
806 {
807     if (!self.overrideDragUpdateBlock)
808         return proposal;
809
810     return [[[UIDropProposal alloc] initWithDropOperation:self.overrideDragUpdateBlock(proposal.operation, session)] autorelease];
811 }
812
813 - (NSArray *)_webView:(WKWebView *)webView adjustedDataInteractionItemProvidersForItemProvider:(NSItemProvider *)itemProvider representingObjects:(NSArray *)representingObjects additionalData:(NSDictionary *)additionalData
814 {
815     return self.convertItemProvidersBlock ? self.convertItemProvidersBlock(itemProvider, representingObjects, additionalData) : @[ itemProvider ];
816 }
817
818 IGNORE_WARNINGS_BEGIN("deprecated-implementations")
819 - (BOOL)_webView:(WKWebView *)webView showCustomSheetForElement:(_WKActivatedElementInfo *)element
820 IGNORE_WARNINGS_END
821 {
822     if (!self.showCustomActionSheetBlock)
823         return NO;
824
825     dispatch_async(dispatch_get_main_queue(), [strongSelf = retainPtr(self)] {
826         [[strongSelf->_webView dragInteractionDelegate] dragInteraction:[strongSelf->_webView dragInteraction] sessionWillBegin:strongSelf->_dragSession.get()];
827         strongSelf->_phase = DragAndDropPhaseBegan;
828         [strongSelf _scheduleAdvanceProgress];
829     });
830
831     return self.showCustomActionSheetBlock(element);
832 }
833
834 - (NSArray<UIDragItem *> *)_webView:(WKWebView *)webView willPerformDropWithSession:(id <UIDropSession>)session
835 {
836     return self.overridePerformDropBlock ? self.overridePerformDropBlock(session) : session.items;
837 }
838
839 - (void)_webView:(WKWebView *)webView didInsertAttachment:(_WKAttachment *)attachment withSource:(NSString *)source
840 {
841     [_insertedAttachments addObject:attachment];
842 }
843
844 - (void)_webView:(WKWebView *)webView didRemoveAttachment:(_WKAttachment *)attachment
845 {
846     [_removedAttachments addObject:attachment];
847 }
848
849 - (WKDragDestinationAction)_webView:(WKWebView *)webView dragDestinationActionMaskForDraggingInfo:(id)draggingInfo
850 {
851     return self.dragDestinationAction;
852 }
853
854 #pragma mark - _WKInputDelegate
855
856 - (BOOL)_webView:(WKWebView *)webView focusShouldStartInputSession:(id <_WKFocusedElementInfo>)info
857 {
858     return _allowsFocusToStartInputSession;
859 }
860
861 - (void)_webView:(WKWebView *)webView didStartInputSession:(id <_WKFormInputSession>)inputSession
862 {
863     _hasStartedInputSession = true;
864 }
865
866 @end
867
868 #endif // ENABLE(DRAG_SUPPORT) && PLATFORM(IOS_FAMILY)