/* * 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. */ #import "config.h" #import "DragAndDropSimulator.h" #if ENABLE(DRAG_SUPPORT) && PLATFORM(IOS_FAMILY) && !PLATFORM(MACCATALYST) #import "InstanceMethodSwizzler.h" #import "PlatformUtilities.h" #import "Test.h" #import "UIKitSPI.h" #import #import #import #import #import #import #import #import #import using namespace TestWebKitAPI; @implementation WKWebView (DragAndDropTesting) - (UIView *)_dragDropInteractionView { return [self valueForKey:@"_currentContentView"]; } - (id )dropInteractionDelegate { return (id )self._dragDropInteractionView; } - (id )dragInteractionDelegate { return (id )self._dragDropInteractionView; } - (UIDropInteraction *)dropInteraction { UIView *interactionView = self._dragDropInteractionView; for (id interaction in interactionView.interactions) { if ([interaction isKindOfClass:[UIDropInteraction class]]) return (UIDropInteraction *)interaction; } return nil; } - (UIDragInteraction *)dragInteraction { UIView *interactionView = self._dragDropInteractionView; for (id interaction in interactionView.interactions) { if ([interaction isKindOfClass:[UIDragInteraction class]]) return (UIDragInteraction *)interaction; } return nil; } @end @implementation MockDragDropSession - (instancetype)initWithItems:(NSArray *)items location:(CGPoint)locationInWindow window:(UIWindow *)window allowMove:(BOOL)allowMove { if (self = [super init]) { _mockItems = items; _mockLocationInWindow = locationInWindow; _window = window; _allowMove = allowMove; } return self; } - (BOOL)allowsMoveOperation { return _allowMove; } - (BOOL)isRestrictedToDraggingApplication { return NO; } - (BOOL)hasItemsConformingToTypeIdentifiers:(NSArray *)typeIdentifiers { for (NSString *typeIdentifier in typeIdentifiers) { BOOL hasItemConformingToType = NO; for (UIDragItem *item in self.items) hasItemConformingToType |= [[item.itemProvider registeredTypeIdentifiers] containsObject:typeIdentifier]; if (!hasItemConformingToType) return NO; } return YES; } - (BOOL)canLoadObjectsOfClass:(Class)aClass { for (UIDragItem *item in self.items) { if ([item.itemProvider canLoadObjectOfClass:aClass]) return YES; } return NO; } - (BOOL)canLoadObjectsOfClasses:(NSArray> *)classes { for (Class aClass in classes) { BOOL canLoad = NO; for (UIDragItem *item in self.items) canLoad |= [item.itemProvider canLoadObjectOfClass:aClass]; if (!canLoad) return NO; } return YES; } - (NSArray *)items { return _mockItems.get(); } - (void)setItems:(NSArray *)items { _mockItems = items; } - (void)addItems:(NSArray *)items { if (![items count]) return; if (![_mockItems count]) _mockItems = items; else _mockItems = [_mockItems arrayByAddingObjectsFromArray:items]; } - (CGPoint)locationInView:(UIView *)view { return [_window convertPoint:_mockLocationInWindow toView:view]; } @end @implementation MockDropSession - (instancetype)initWithProviders:(NSArray *)providers location:(CGPoint)locationInWindow window:(UIWindow *)window allowMove:(BOOL)allowMove { auto items = adoptNS([[NSMutableArray alloc] init]); for (NSItemProvider *itemProvider in providers) [items addObject:adoptNS([[UIDragItem alloc] initWithItemProvider:itemProvider]).get()]; return [super initWithItems:items.get() location:locationInWindow window:window allowMove:allowMove]; } - (BOOL)isLocal { return YES; } - (NSProgress *)progress { return [NSProgress discreteProgressWithTotalUnitCount:100]; } - (void)setProgressIndicatorStyle:(UIDropSessionProgressIndicatorStyle)progressIndicatorStyle { } - (UIDropSessionProgressIndicatorStyle)progressIndicatorStyle { return UIDropSessionProgressIndicatorStyleNone; } - (NSUInteger)operationMask { return 0; } - (id )localDragSession { return nil; } - (BOOL)hasItemsConformingToTypeIdentifier:(NSString *)typeIdentifier { ASSERT_NOT_REACHED(); return NO; } - (BOOL)canCreateItemsOfClass:(Class)aClass { ASSERT_NOT_REACHED(); return NO; } - (NSProgress *)loadObjectsOfClass:(Class)aClass completion:(void(^)(NSArray<__kindof id > *objects))completion { ASSERT_NOT_REACHED(); return nil; } @end @implementation MockDragSession { RetainPtr _localContext; } - (instancetype)initWithWindow:(UIWindow *)window allowMove:(BOOL)allowMove { return [super initWithItems:@[ ] location:CGPointZero window:window allowMove:allowMove]; } - (NSUInteger)localOperationMask { ASSERT_NOT_REACHED(); return 0; } - (NSUInteger)externalOperationMask { ASSERT_NOT_REACHED(); return 0; } - (id)session { return nil; } - (id)localContext { return _localContext.get(); } - (void)setLocalContext:(id)localContext { _localContext = localContext; } @end static double progressIncrementStep = 0.033; static double progressTimeStep = 0.016; static NSString *TestWebKitAPISimulateCancelAllTouchesNotificationName = @"TestWebKitAPISimulateCancelAllTouchesNotificationName"; static NSArray *dragAndDropEventNames() { static NSArray *eventNames = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^() { eventNames = @[ @"dragenter", @"dragover", @"drop", @"dragleave", @"dragstart" ]; }); return eventNames; } @interface DragAndDropSimulatorApplication : UIApplication @end @implementation DragAndDropSimulatorApplication IGNORE_WARNINGS_BEGIN("deprecated-implementations") - (void)_cancelAllTouches { [[NSNotificationCenter defaultCenter] postNotificationName:TestWebKitAPISimulateCancelAllTouchesNotificationName object:nil]; } IGNORE_WARNINGS_END @end @interface DragAndDropSimulator () @end @implementation DragAndDropSimulator { RetainPtr _webView; RetainPtr _dragSession; RetainPtr _dropSession; RetainPtr _observedEventNames; RetainPtr _externalItemProviders; RetainPtr _sourceItemProviders; CGRect _finalSelectionStartRect; CGPoint _startLocation; CGPoint _endLocation; CGRect _lastKnownDragCaretRect; RetainPtr>_remainingAdditionalItemRequestLocationsByProgress; RetainPtr>_queuedAdditionalItemRequestLocations; RetainPtr _liftPreviews; RetainPtr> _cancellationPreviews; RetainPtr _dropPreviews; RetainPtr _delayedDropPreviews; RetainPtr _defaultDropPreviewsForExternalItems; RetainPtr> _insertedAttachments; RetainPtr> _removedAttachments; bool _hasStartedInputSession; double _currentProgress; bool _isDoneWithCurrentRun; bool _isDoneWaitingForDelayedDropPreviews; DragAndDropPhase _phase; RetainPtr _lastKnownDropProposal; BlockPtr _showCustomActionSheetBlock; BlockPtr _convertItemProvidersBlock; BlockPtr)> _overridePerformDropBlock; BlockPtr _overrideDragUpdateBlock; BlockPtr _dropCompletionBlock; BlockPtr _sessionWillBeginBlock; Vector> _dropAnimationCompletionBlocks; } - (instancetype)initWithWebViewFrame:(CGRect)frame { return [self initWithWebViewFrame:frame configuration:nil]; } - (instancetype)initWithWebViewFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration { if (configuration) return [self initWithWebView:adoptNS([[TestWKWebView alloc] initWithFrame:frame configuration:configuration]).get()]; return [self initWithWebView:adoptNS([[TestWKWebView alloc] initWithFrame:frame]).get()]; } - (instancetype)initWithWebView:(TestWKWebView *)webView { if (self = [super init]) { _webView = webView; _shouldEnsureUIApplication = NO; _shouldBecomeFirstResponder = YES; _shouldAllowMoveOperation = YES; _dropAnimationTiming = DropAnimationShouldFinishAfterHandlingDrop; [_webView setUIDelegate:self]; [_webView _setInputDelegate:self]; self.dragDestinationAction = WKDragDestinationActionAny & ~WKDragDestinationActionLoad; } return self; } - (void)dealloc { if ([_webView UIDelegate] == self) [_webView setUIDelegate:nil]; if ([_webView _inputDelegate] == self) [_webView _setInputDelegate:nil]; [super dealloc]; } - (void)_resetSimulatedState { _phase = DragAndDropPhaseBeginning; _currentProgress = 0; _isDoneWithCurrentRun = false; _isDoneWaitingForDelayedDropPreviews = true; _observedEventNames = adoptNS([[NSMutableArray alloc] init]); _insertedAttachments = adoptNS([[NSMutableArray alloc] init]); _removedAttachments = adoptNS([[NSMutableArray alloc] init]); _finalSelectionStartRect = CGRectNull; _dragSession = nil; _dropSession = nil; _lastKnownDropProposal = nil; _lastKnownDragCaretRect = CGRectZero; _remainingAdditionalItemRequestLocationsByProgress = nil; _queuedAdditionalItemRequestLocations = adoptNS([[NSMutableArray alloc] init]); _liftPreviews = adoptNS([[NSMutableArray alloc] init]); _dropPreviews = adoptNS([[NSMutableArray alloc] init]); _cancellationPreviews = adoptNS([[NSMutableArray alloc] init]); _delayedDropPreviews = adoptNS([[NSMutableArray alloc] init]); _hasStartedInputSession = false; } - (NSArray *)observedEventNames { return _observedEventNames.get(); } - (UIDropProposal *)lastKnownDropProposal { return _lastKnownDropProposal.get(); } - (void)simulateAllTouchesCanceled:(NSNotification *)notification { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_advanceProgress) object:nil]; _phase = DragAndDropPhaseCancelled; _currentProgress = 1; _isDoneWithCurrentRun = true; if (_dragSession) [[_webView dragInteractionDelegate] dragInteraction:[_webView dragInteraction] session:_dragSession.get() didEndWithOperation:UIDropOperationCancel]; } - (void)runFrom:(CGPoint)startLocation to:(CGPoint)endLocation { [self runFrom:startLocation to:endLocation additionalItemRequestLocations:nil]; } - (void)runFrom:(CGPoint)startLocation to:(CGPoint)endLocation additionalItemRequestLocations:(ProgressToCGPointValueMap)additionalItemRequestLocations { NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; [defaultCenter addObserver:self selector:@selector(simulateAllTouchesCanceled:) name:TestWebKitAPISimulateCancelAllTouchesNotificationName object:nil]; if (_shouldEnsureUIApplication) UIApplicationInstantiateSingleton([DragAndDropSimulatorApplication class]); if (_shouldBecomeFirstResponder) [_webView becomeFirstResponder]; [self _resetSimulatedState]; if (additionalItemRequestLocations) _remainingAdditionalItemRequestLocationsByProgress = adoptNS([additionalItemRequestLocations mutableCopy]); for (NSString *eventName in dragAndDropEventNames()) { [_webView performAfterReceivingMessage:eventName action:[strongSelf = retainPtr(self), name = retainPtr(eventName)] { [strongSelf->_observedEventNames addObject:name.get()]; }]; } _startLocation = startLocation; _endLocation = endLocation; if (self.externalItemProviders.count) { _dropSession = adoptNS([[MockDropSession alloc] initWithProviders:self.externalItemProviders location:_startLocation window:[_webView window] allowMove:self.shouldAllowMoveOperation]); _phase = DragAndDropPhaseBegan; [self _advanceProgress]; } else { _dragSession = adoptNS([[MockDragSession alloc] initWithWindow:[_webView window] allowMove:self.shouldAllowMoveOperation]); [_dragSession setMockLocationInWindow:_startLocation]; [(id )[_webView dragInteractionDelegate] _dragInteraction:[_webView dragInteraction] prepareForSession:_dragSession.get() completion:[strongSelf = retainPtr(self)] { if (strongSelf->_phase == DragAndDropPhaseCancelled) return; strongSelf->_phase = DragAndDropPhaseBeginning; [strongSelf _advanceProgress]; }]; } Util::run(&_isDoneWithCurrentRun); Util::run(&_isDoneWaitingForDelayedDropPreviews); [_webView clearMessageHandlers:dragAndDropEventNames()]; [_webView waitForNextPresentationUpdate]; auto contentView = [_webView textInputContentView]; _finalSelectionStartRect = [contentView caretRectForPosition:contentView.selectedTextRange.start]; [defaultCenter removeObserver:self]; } - (void)_concludeDropAndPerformOperationIfNecessary { _lastKnownDragCaretRect = [_webView _dragCaretRect]; auto operation = [_lastKnownDropProposal operation]; if (operation != UIDropOperationCancel && operation != UIDropOperationForbidden) { NSInteger dropPreviewIndex = 0; __block NSUInteger numberOfPendingPreviews = [_dropSession items].count; _isDoneWaitingForDelayedDropPreviews = !numberOfPendingPreviews; BOOL canUseDefaultDropPreviewsForExternalItems = [_defaultDropPreviewsForExternalItems count] == [_dropSession items].count; for (UIDragItem *item in [_dropSession items]) { RetainPtr defaultPreview; if (canUseDefaultDropPreviewsForExternalItems) defaultPreview = [_defaultDropPreviewsForExternalItems objectAtIndex:dropPreviewIndex]; else { // Just fall back to an arbitrary non-null drag preview if the test didn't specify one. defaultPreview = adoptNS([[UITargetedDragPreview alloc] initWithView:_webView.get()]); } id delegate = (id )[_webView dropInteractionDelegate]; UIDropInteraction *interaction = [_webView dropInteraction]; [_dropPreviews addObject:[delegate dropInteraction:interaction previewForDroppingItem:item withDefault:defaultPreview.get()] ?: NSNull.null]; [_delayedDropPreviews addObject:NSNull.null]; [delegate _dropInteraction:interaction delayedPreviewProviderForDroppingItem:item previewProvider:^(UITargetedDragPreview *preview) { if (preview) [_delayedDropPreviews setObject:preview atIndexedSubscript:dropPreviewIndex]; if (!--numberOfPendingPreviews) { _isDoneWaitingForDelayedDropPreviews = true; [self _expectNoDropPreviewsWithUnparentedContainerViews]; } }]; ++dropPreviewIndex; } [self _expectNoDropPreviewsWithUnparentedContainerViews]; [[_webView dropInteractionDelegate] dropInteraction:[_webView dropInteraction] performDrop:_dropSession.get()]; _phase = DragAndDropPhasePerformingDrop; for (UIDragItem *item in [_dropSession items]) [[_webView dropInteractionDelegate] dropInteraction:[_webView dropInteraction] item:item willAnimateDropWithAnimator:self]; if (_dropAnimationTiming == DropAnimationShouldFinishBeforeHandlingDrop) { [_webView evaluateJavaScript:@"" completionHandler:^(id, NSError *) { // We need to at least ensure one round trip to the web process and back, to ensure that the UI process will have received any image placeholders // that were just inserted as a result of performing the drop. However, this is guaranteed to run before the UI process receives the drop completion // message, since item provider loading is asynchronous. [self _invokeDropAnimationCompletionBlocksAndConcludeDrop]; }]; } } else { _isDoneWithCurrentRun = true; _phase = DragAndDropPhaseCancelled; [[_dropSession items] enumerateObjectsUsingBlock:^(UIDragItem *item, NSUInteger index, BOOL *) { UITargetedDragPreview *defaultPreview = nil; if ([_liftPreviews count] && [[_liftPreviews objectAtIndex:index] isEqual:NSNull.null]) defaultPreview = [_liftPreviews objectAtIndex:index]; UITargetedDragPreview *preview = [[_webView dragInteractionDelegate] dragInteraction:[_webView dragInteraction] previewForCancellingItem:item withDefault:defaultPreview]; if (preview) [_cancellationPreviews addObject:preview]; }]; [[_webView dropInteractionDelegate] dropInteraction:[_webView dropInteraction] concludeDrop:_dropSession.get()]; } [[_webView dropInteractionDelegate] dropInteraction:[_webView dropInteraction] sessionDidEnd:_dropSession.get()]; if (_dragSession) { auto delegate = [_webView dragInteractionDelegate]; [delegate dragInteraction:[_webView dragInteraction] session:_dragSession.get() didEndWithOperation:operation]; if ([delegate respondsToSelector:@selector(_clearToken:)]) [(id )delegate _clearToken:nil]; [_webView becomeFirstResponder]; } } - (void)_enqueuePendingAdditionalItemRequestLocations { NSMutableArray *progressValuesToRemove = [NSMutableArray array]; for (NSNumber *progressValue in _remainingAdditionalItemRequestLocationsByProgress.get()) { double progress = progressValue.doubleValue; if (progress > _currentProgress) continue; [progressValuesToRemove addObject:progressValue]; [_queuedAdditionalItemRequestLocations addObject:[_remainingAdditionalItemRequestLocationsByProgress objectForKey:progressValue]]; } for (NSNumber *progressToRemove in progressValuesToRemove) [_remainingAdditionalItemRequestLocationsByProgress removeObjectForKey:progressToRemove]; } - (BOOL)_sendQueuedAdditionalItemRequest { if (![_queuedAdditionalItemRequestLocations count]) return NO; RetainPtr requestLocationValue = [_queuedAdditionalItemRequestLocations objectAtIndex:0]; [_queuedAdditionalItemRequestLocations removeObjectAtIndex:0]; auto requestLocation = [[_webView window] convertPoint:[requestLocationValue CGPointValue] toView:_webView.get()]; [(id )[_webView dragInteractionDelegate] _dragInteraction:[_webView dragInteraction] itemsForAddingToSession:_dragSession.get() withTouchAtPoint:requestLocation completion:[dragSession = _dragSession, dropSession = _dropSession] (NSArray *items) { [dragSession addItems:items]; [dropSession addItems:items]; }]; return YES; } - (void)_advanceProgress { [self _enqueuePendingAdditionalItemRequestLocations]; if ([self _sendQueuedAdditionalItemRequest]) { [self _scheduleAdvanceProgress]; return; } _lastKnownDragCaretRect = [_webView _dragCaretRect]; _currentProgress += progressIncrementStep; CGPoint locationInWindow = self._currentLocation; [_dragSession setMockLocationInWindow:locationInWindow]; [_dropSession setMockLocationInWindow:locationInWindow]; if (_currentProgress >= 1) { _currentProgress = 1; [self _concludeDropAndPerformOperationIfNecessary]; return; } switch (_phase) { case DragAndDropPhaseBeginning: { NSMutableArray *itemProviders = [NSMutableArray array]; NSArray *items = [[_webView dragInteractionDelegate] dragInteraction:[_webView dragInteraction] itemsForBeginningSession:_dragSession.get()]; if (!items.count) { _phase = DragAndDropPhaseCancelled; _currentProgress = 1; _isDoneWithCurrentRun = true; return; } for (UIDragItem *item in items) { [itemProviders addObject:item.itemProvider]; UITargetedDragPreview *liftPreview = [[_webView dragInteractionDelegate] dragInteraction:[_webView dragInteraction] previewForLiftingItem:item session:_dragSession.get()]; EXPECT_TRUE(liftPreview || ![_webView window]); [_liftPreviews addObject:liftPreview ?: NSNull.null]; } _dropSession = adoptNS([[MockDropSession alloc] initWithProviders:itemProviders location:self._currentLocation window:[_webView window] allowMove:self.shouldAllowMoveOperation]); [_dragSession setItems:items]; _sourceItemProviders = itemProviders; if (self.showCustomActionSheetBlock) { // Defer progress until the custom action sheet is dismissed. auto startLocationInView = [[_webView window] convertPoint:_startLocation toView:_webView.get()]; [_webView _simulateLongPressActionAtLocation:startLocationInView]; return; } auto delegate = [_webView dragInteractionDelegate]; if ([delegate respondsToSelector:@selector(_preserveFocusWithToken:destructively:)]) [(id )delegate _preserveFocusWithToken:nil destructively:NO]; [_webView resignFirstResponder]; [delegate dragInteraction:[_webView dragInteraction] sessionWillBegin:_dragSession.get()]; RetainPtr retainedWebView = _webView; dispatch_async(dispatch_get_main_queue(), ^() { [retainedWebView resignFirstResponder]; }); _phase = DragAndDropPhaseBegan; break; } case DragAndDropPhaseBegan: [[_webView dropInteractionDelegate] dropInteraction:[_webView dropInteraction] sessionDidEnter:_dropSession.get()]; _phase = DragAndDropPhaseEntered; break; case DragAndDropPhaseEntered: { _lastKnownDropProposal = [[_webView dropInteractionDelegate] dropInteraction:[_webView dropInteraction] sessionDidUpdate:_dropSession.get()]; [_webView waitForNextPresentationUpdate]; if (![self shouldAllowMoveOperation] && [_lastKnownDropProposal operation] == UIDropOperationMove) _lastKnownDropProposal = adoptNS([[UIDropProposal alloc] initWithDropOperation:UIDropOperationCancel]); break; } default: break; } [self _scheduleAdvanceProgress]; } - (void)clearExternalDragInformation { _externalItemProviders = nil; _defaultDropPreviewsForExternalItems = nil; } - (CGPoint)_currentLocation { CGFloat distanceX = _endLocation.x - _startLocation.x; CGFloat distanceY = _endLocation.y - _startLocation.y; return CGPointMake(_startLocation.x + _currentProgress * distanceX, _startLocation.y + _currentProgress * distanceY); } - (void)_scheduleAdvanceProgress { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_advanceProgress) object:nil]; [self performSelector:@selector(_advanceProgress) withObject:nil afterDelay:progressTimeStep]; } - (NSArray *)sourceItemProviders { return _sourceItemProviders.get(); } - (NSArray *)externalItemProviders { return _externalItemProviders.get(); } - (void)setExternalItemProviders:(NSArray *)externalItemProviders { _externalItemProviders = adoptNS([externalItemProviders copy]); } - (void)setExternalItemProviders:(NSArray *)itemProviders defaultDropPreviews:(NSArray *)previews { ASSERT(itemProviders.count == previews.count); self.externalItemProviders = itemProviders; _defaultDropPreviewsForExternalItems = adoptNS(previews.copy); } - (DragAndDropPhase)phase { return _phase; } - (NSArray *)liftPreviews { return _liftPreviews.get(); } - (NSArray *)cancellationPreviews { return _cancellationPreviews.get(); } - (NSArray *)dropPreviews { return _dropPreviews.get(); } - (NSArray *)delayedDropPreviews { return _delayedDropPreviews.get(); } - (CGRect)lastKnownDragCaretRect { return _lastKnownDragCaretRect; } - (void)ensureInputSession { Util::run(&_hasStartedInputSession); } - (NSArray<_WKAttachment *> *)insertedAttachments { return _insertedAttachments.get(); } - (NSArray<_WKAttachment *> *)removedAttachments { return _removedAttachments.get(); } - (void)endDataTransfer { [[_webView dragInteractionDelegate] dragInteraction:[_webView dragInteraction] sessionDidTransferItems:_dragSession.get()]; } - (TestWKWebView *)webView { return _webView.get(); } - (void)setShowCustomActionSheetBlock:(BOOL(^)(_WKActivatedElementInfo *))showCustomActionSheetBlock { _showCustomActionSheetBlock = showCustomActionSheetBlock; } - (BOOL(^)(_WKActivatedElementInfo *))showCustomActionSheetBlock { return _showCustomActionSheetBlock.get(); } - (void)setConvertItemProvidersBlock:(NSArray *(^)(NSItemProvider *, NSArray *, NSDictionary *))convertItemProvidersBlock { _convertItemProvidersBlock = convertItemProvidersBlock; } - (NSArray *(^)(NSItemProvider *, NSArray *, NSDictionary *))convertItemProvidersBlock { return _convertItemProvidersBlock.get(); } - (void)setOverridePerformDropBlock:(NSArray *(^)(id ))overridePerformDropBlock { _overridePerformDropBlock = overridePerformDropBlock; } - (NSArray *(^)(id ))overridePerformDropBlock { return _overridePerformDropBlock.get(); } - (void)setOverrideDragUpdateBlock:(UIDropOperation(^)(UIDropOperation, id ))overrideDragUpdateBlock { _overrideDragUpdateBlock = overrideDragUpdateBlock; } - (UIDropOperation(^)(UIDropOperation, id ))overrideDragUpdateBlock { return _overrideDragUpdateBlock.get(); } - (void)setDropCompletionBlock:(void(^)(BOOL, NSArray *))dropCompletionBlock { _dropCompletionBlock = dropCompletionBlock; } - (void(^)(BOOL, NSArray *))dropCompletionBlock { return _dropCompletionBlock.get(); } - (void)setSessionWillBeginBlock:(dispatch_block_t)block { _sessionWillBeginBlock = block; } - (dispatch_block_t)sessionWillBeginBlock { return _sessionWillBeginBlock.get(); } - (void)addAnimations:(void (^)())animations { // This is not implemented by the drag-and-drop simulator yet, since WebKit doesn't make use of // "alongside" animations during drop. ASSERT_NOT_REACHED(); } - (void)addCompletion:(void (^)(UIViewAnimatingPosition))completion { _dropAnimationCompletionBlocks.append(makeBlockPtr(completion)); } - (void)_expectNoDropPreviewsWithUnparentedContainerViews { auto checkDropPreview = [&](id dropPreviewOrNull) { if (![dropPreviewOrNull isKindOfClass:UITargetedPreview.class]) return; auto *previewContainer = [(UITargetedDragPreview *)dropPreviewOrNull target].container; if (!previewContainer) return; if ([previewContainer isKindOfClass:UIWindow.class]) return; EXPECT_NOT_NULL(previewContainer.window); }; for (id dropPreviewOrNull in _dropPreviews.get()) checkDropPreview(dropPreviewOrNull); for (id dropPreviewOrNull in _delayedDropPreviews.get()) checkDropPreview(dropPreviewOrNull); } - (void)_invokeDropAnimationCompletionBlocksAndConcludeDrop { [self _expectNoDropPreviewsWithUnparentedContainerViews]; for (auto block : std::exchange(_dropAnimationCompletionBlocks, { })) block(UIViewAnimatingPositionEnd); [[_webView dropInteractionDelegate] dropInteraction:[_webView dropInteraction] concludeDrop:_dropSession.get()]; } - (BOOL)containsDraggedType:(NSString *)expectedType { for (NSItemProvider *itemProvider in self.sourceItemProviders) { for (NSString *type in itemProvider.registeredTypeIdentifiers) { if ([type isEqualToString:expectedType]) return YES; } } return NO; } #pragma mark - WKUIDelegatePrivate - (void)_webView:(WKWebView *)webView dataInteraction:(UIDragInteraction *)interaction sessionWillBegin:(id )session { if (_sessionWillBeginBlock) _sessionWillBeginBlock(); } - (void)_webView:(WKWebView *)webView dataInteractionOperationWasHandled:(BOOL)handled forSession:(id)session itemProviders:(NSArray *)itemProviders { if (self.dropCompletionBlock) self.dropCompletionBlock(handled, itemProviders); if (_dropAnimationTiming == DropAnimationShouldFinishBeforeHandlingDrop) { _isDoneWithCurrentRun = true; return; } [_webView _doAfterReceivingEditDragSnapshotForTesting:^{ [self _invokeDropAnimationCompletionBlocksAndConcludeDrop]; _isDoneWithCurrentRun = true; }]; } - (UIDropProposal *)_webView:(WKWebView *)webView willUpdateDropProposalToProposal:(UIDropProposal *)proposal forSession:(id )session { if (!self.overrideDragUpdateBlock) return proposal; return adoptNS([[UIDropProposal alloc] initWithDropOperation:self.overrideDragUpdateBlock(proposal.operation, session)]).autorelease(); } - (NSArray *)_webView:(WKWebView *)webView adjustedDataInteractionItemProvidersForItemProvider:(NSItemProvider *)itemProvider representingObjects:(NSArray *)representingObjects additionalData:(NSDictionary *)additionalData { return self.convertItemProvidersBlock ? self.convertItemProvidersBlock(itemProvider, representingObjects, additionalData) : @[ itemProvider ]; } IGNORE_WARNINGS_BEGIN("deprecated-implementations") - (BOOL)_webView:(WKWebView *)webView showCustomSheetForElement:(_WKActivatedElementInfo *)element IGNORE_WARNINGS_END { if (!self.showCustomActionSheetBlock) return NO; dispatch_async(dispatch_get_main_queue(), [strongSelf = retainPtr(self)] { [[strongSelf->_webView dragInteractionDelegate] dragInteraction:[strongSelf->_webView dragInteraction] sessionWillBegin:strongSelf->_dragSession.get()]; strongSelf->_phase = DragAndDropPhaseBegan; [strongSelf _scheduleAdvanceProgress]; }); return self.showCustomActionSheetBlock(element); } - (NSArray *)_webView:(WKWebView *)webView willPerformDropWithSession:(id )session { return self.overridePerformDropBlock ? self.overridePerformDropBlock(session) : session.items; } - (void)_webView:(WKWebView *)webView didInsertAttachment:(_WKAttachment *)attachment withSource:(NSString *)source { [_insertedAttachments addObject:attachment]; } - (void)_webView:(WKWebView *)webView didRemoveAttachment:(_WKAttachment *)attachment { [_removedAttachments addObject:attachment]; } - (WKDragDestinationAction)_webView:(WKWebView *)webView dragDestinationActionMaskForDraggingInfo:(id)draggingInfo { return self.dragDestinationAction; } #pragma mark - _WKInputDelegate - (BOOL)_webView:(WKWebView *)webView focusShouldStartInputSession:(id <_WKFocusedElementInfo>)info { return _allowsFocusToStartInputSession; } - (void)_webView:(WKWebView *)webView didStartInputSession:(id <_WKFormInputSession>)inputSession { _hasStartedInputSession = true; } @end #endif // ENABLE(DRAG_SUPPORT) && PLATFORM(IOS_FAMILY) && !PLATFORM(MACCATALYST)