| /* |
| * Copyright (C) 2018 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(MAC) |
| |
| #import "PlatformUtilities.h" |
| #import "TestDraggingInfo.h" |
| #import "TestWKWebView.h" |
| #import <WebKit/WKWebViewPrivateForTesting.h> |
| #import <cmath> |
| #import <wtf/WeakObjCPtr.h> |
| |
| @class DragAndDropTestWKWebView; |
| |
| @interface DragAndDropSimulator () |
| - (void)beginDraggingSessionInWebView:(DragAndDropTestWKWebView *)webView withItems:(NSArray<NSDraggingItem *> *)items source:(id<NSDraggingSource>)source; |
| - (void)performDragInWebView:(DragAndDropTestWKWebView *)webView atLocation:(NSPoint)viewLocation withImage:(NSImage *)image pasteboard:(NSPasteboard *)pasteboard source:(id)source; |
| @property (nonatomic, readonly) NSDraggingSession *draggingSession; |
| @end |
| |
| @interface DragAndDropTestWKWebView : TestWKWebView |
| @end |
| |
| @implementation DragAndDropTestWKWebView { |
| WeakObjCPtr<DragAndDropSimulator> _dragAndDropSimulator; |
| } |
| |
| - (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration simulator:(DragAndDropSimulator *)simulator |
| { |
| if (self = [super initWithFrame:frame configuration:configuration]) |
| _dragAndDropSimulator = simulator; |
| return self; |
| } |
| |
| IGNORE_WARNINGS_BEGIN("deprecated-implementations") |
| - (void)dragImage:(NSImage *)image at:(NSPoint)viewLocation offset:(NSSize)initialOffset event:(NSEvent *)event pasteboard:(NSPasteboard *)pboard source:(id)sourceObj slideBack:(BOOL)slideFlag |
| IGNORE_WARNINGS_END |
| { |
| [_dragAndDropSimulator performDragInWebView:self atLocation:viewLocation withImage:image pasteboard:pboard source:sourceObj]; |
| } |
| |
| - (NSDraggingSession *)beginDraggingSessionWithItems:(NSArray<NSDraggingItem *> *)items event:(NSEvent *)event source:(id<NSDraggingSource>)source |
| { |
| [_dragAndDropSimulator beginDraggingSessionInWebView:self withItems:items source:source]; |
| return [_dragAndDropSimulator draggingSession]; |
| } |
| |
| @end |
| |
| // This exceeds the default drag hysteresis of all potential drag types. |
| const double initialMouseDragDistance = 45; |
| const double dragUpdateProgressIncrement = 0.05; |
| |
| static NSImage *defaultExternalDragImage() |
| { |
| return [[[NSImage alloc] initWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"icon" withExtension:@"png" subdirectory:@"TestWebKitAPI.resources"]] autorelease]; |
| } |
| |
| @implementation DragAndDropSimulator { |
| RetainPtr<DragAndDropTestWKWebView> _webView; |
| RetainPtr<TestDraggingInfo> _draggingInfo; |
| RetainPtr<NSPasteboard> _externalDragPasteboard; |
| RetainPtr<NSImage> _externalDragImage; |
| RetainPtr<NSArray<NSURL *>> _externalPromisedFiles; |
| RetainPtr<NSMutableArray<_WKAttachment *>> _insertedAttachments; |
| RetainPtr<NSMutableArray<_WKAttachment *>> _removedAttachments; |
| RetainPtr<NSMutableArray<NSURL *>> _filePromiseDestinationURLs; |
| RetainPtr<NSDraggingSession> _draggingSession; |
| RetainPtr<NSMutableArray<NSFilePromiseProvider *>> _filePromiseProviders; |
| BlockPtr<void()> _willEndDraggingHandler; |
| NSPoint _startLocationInWindow; |
| NSPoint _endLocationInWindow; |
| double _progress; |
| bool _doneWaitingForDraggingSession; |
| bool _doneWaitingForDrop; |
| } |
| |
| @synthesize currentDragOperation=_currentDragOperation; |
| @synthesize initialDragImageLocationInView=_initialDragImageLocationInView; |
| |
| - (instancetype)initWithWebViewFrame:(CGRect)frame |
| { |
| return [self initWithWebViewFrame:frame configuration:nil]; |
| } |
| |
| - (instancetype)initWithWebViewFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration |
| { |
| if (self = [super init]) { |
| _webView = adoptNS([[DragAndDropTestWKWebView alloc] initWithFrame:frame configuration:configuration ?: [[[WKWebViewConfiguration alloc] init] autorelease] simulator:self]); |
| _filePromiseDestinationURLs = adoptNS([NSMutableArray new]); |
| [_webView setUIDelegate:self]; |
| self.dragDestinationAction = WKDragDestinationActionAny & ~WKDragDestinationActionLoad; |
| } |
| return self; |
| } |
| |
| - (void)dealloc |
| { |
| for (NSURL *url in _filePromiseDestinationURLs.get()) |
| [[NSFileManager defaultManager] removeItemAtURL:url error:nil]; |
| |
| [super dealloc]; |
| } |
| |
| - (NSPoint)flipAboutXAxisInHostWindow:(NSPoint)point |
| { |
| return { point.x, NSHeight([[_webView hostWindow] frame]) - point.y }; |
| } |
| |
| - (NSPoint)locationInViewForCurrentProgress |
| { |
| return { |
| _startLocationInWindow.x + (_endLocationInWindow.x - _startLocationInWindow.x) * _progress, |
| _startLocationInWindow.y + (_endLocationInWindow.y - _startLocationInWindow.y) * _progress |
| }; |
| } |
| |
| - (double)initialProgressForMouseDrag |
| { |
| double totalDistance = std::hypot(_startLocationInWindow.x - _endLocationInWindow.x, _startLocationInWindow.y - _endLocationInWindow.y); |
| return !totalDistance ? 1 : std::min<double>(1, initialMouseDragDistance / totalDistance); |
| } |
| |
| - (void)runFrom:(CGPoint)flippedStartLocation to:(CGPoint)flippedEndLocation |
| { |
| _insertedAttachments = adoptNS([NSMutableArray new]); |
| _removedAttachments = adoptNS([NSMutableArray new]); |
| _doneWaitingForDraggingSession = true; |
| _doneWaitingForDrop = true; |
| _startLocationInWindow = [self flipAboutXAxisInHostWindow:flippedStartLocation]; |
| _endLocationInWindow = [self flipAboutXAxisInHostWindow:flippedEndLocation]; |
| _currentDragOperation = NSDragOperationNone; |
| _draggingInfo = nil; |
| _draggingSession = nil; |
| _progress = 0; |
| _filePromiseProviders = adoptNS([NSMutableArray new]); |
| |
| if (NSPasteboard *pasteboard = self.externalDragPasteboard) { |
| NSPoint startLocationInView = [_webView convertPoint:_startLocationInWindow fromView:nil]; |
| NSImage *dragImage = self.externalDragImage ?: defaultExternalDragImage(); |
| [self performDragInWebView:_webView.get() atLocation:startLocationInView withImage:dragImage pasteboard:pasteboard source:nil]; |
| TestWebKitAPI::Util::run(&_doneWaitingForDrop); |
| return; |
| } |
| |
| _progress = [self initialProgressForMouseDrag]; |
| if (_progress == 1) { |
| [NSException raise:@"DragAndDropSimulator" format:@"Drag start (%@) and drag end (%@) locations are too close!", NSStringFromPoint(flippedStartLocation), NSStringFromPoint(flippedEndLocation)]; |
| return; |
| } |
| |
| [_webView mouseEnterAtPoint:_startLocationInWindow]; |
| [_webView mouseMoveToPoint:_startLocationInWindow withFlags:0]; |
| [_webView mouseDownAtPoint:_startLocationInWindow simulatePressure:NO]; |
| // Make sure that we exceed the minimum 150ms delay between handling mousedown and drag when dragging a text selection. |
| [_webView setEventTimestampOffset:0.25]; |
| [_webView mouseDragToPoint:[self locationInViewForCurrentProgress]]; |
| [_webView waitForPendingMouseEvents]; |
| |
| TestWebKitAPI::Util::run(&_doneWaitingForDraggingSession); |
| |
| [_webView mouseUpAtPoint:_endLocationInWindow]; |
| [_webView waitForPendingMouseEvents]; |
| |
| TestWebKitAPI::Util::run(&_doneWaitingForDrop); |
| [_webView setEventTimestampOffset:0]; |
| } |
| |
| - (void)beginDraggingSessionInWebView:(DragAndDropTestWKWebView *)webView withItems:(NSArray<NSDraggingItem *> *)items source:(id<NSDraggingSource>)source |
| { |
| NSMutableArray *pasteboardObjects = [NSMutableArray arrayWithCapacity:items.count]; |
| NSMutableArray<NSString *> *promisedFileTypes = [NSMutableArray array]; |
| for (NSDraggingItem *item in items) { |
| id pasteboardObject = item.item; |
| [pasteboardObjects addObject:pasteboardObject]; |
| if ([pasteboardObject isKindOfClass:[NSFilePromiseProvider class]]) { |
| [_filePromiseProviders addObject:pasteboardObject]; |
| [promisedFileTypes addObject:[(NSFilePromiseProvider *)pasteboardObject fileType]]; |
| } |
| } |
| |
| NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard]; |
| [pasteboard clearContents]; |
| [pasteboard writeObjects:pasteboardObjects]; |
| if (promisedFileTypes.count) { |
| // Match AppKit behavior by writing legacy file promise types to the pasteboard as well. |
| [pasteboard setPropertyList:promisedFileTypes forType:NSFilesPromisePboardType]; |
| [pasteboard addTypes:@[@"NSPromiseContentsPboardType", (NSString *)kPasteboardTypeFileURLPromise] owner:nil]; |
| } |
| |
| _draggingSession = adoptNS([[NSDraggingSession alloc] init]); |
| _doneWaitingForDraggingSession = false; |
| _initialDragImageLocationInView = items[0].draggingFrame.origin; |
| id dragImageContents = items[0].imageComponents.firstObject.contents; |
| [self initializeDraggingInfo:pasteboard dragImage:[dragImageContents isKindOfClass:[NSImage class]] ? dragImageContents : nil source:source]; |
| |
| _currentDragOperation = [_webView draggingEntered:_draggingInfo.get()]; |
| [_webView waitForNextPresentationUpdate]; |
| [self performSelector:@selector(continueDragSession) withObject:nil afterDelay:0]; |
| } |
| |
| - (void)continueDragSession |
| { |
| _progress = std::min<double>(1, _progress + dragUpdateProgressIncrement); |
| |
| if (_progress < 1) { |
| [_draggingInfo setDraggingLocation:[self locationInViewForCurrentProgress]]; |
| _currentDragOperation = [_webView draggingUpdated:_draggingInfo.get()]; |
| [_webView waitForNextPresentationUpdate]; |
| [self performSelector:@selector(continueDragSession) withObject:nil afterDelay:0]; |
| return; |
| } |
| |
| [_draggingInfo setDraggingLocation:_endLocationInWindow]; |
| |
| if (_willEndDraggingHandler) |
| _willEndDraggingHandler(); |
| |
| if (_currentDragOperation != NSDragOperationNone && [_webView prepareForDragOperation:_draggingInfo.get()]) { |
| _doneWaitingForDrop = false; |
| [_webView performDragOperation:_draggingInfo.get()]; |
| } else if (_currentDragOperation == NSDragOperationNone) |
| [_webView draggingExited:_draggingInfo.get()]; |
| [_webView waitForNextPresentationUpdate]; |
| [(id <NSDraggingSource>)_webView.get() draggingSession:_draggingSession.get() endedAtPoint:_endLocationInWindow operation:_currentDragOperation]; |
| |
| _doneWaitingForDraggingSession = true; |
| } |
| |
| - (void)performDragInWebView:(DragAndDropTestWKWebView *)webView atLocation:(NSPoint)viewLocation withImage:(NSImage *)image pasteboard:(NSPasteboard *)pasteboard source:(id)source |
| { |
| _initialDragImageLocationInView = viewLocation; |
| [self initializeDraggingInfo:pasteboard dragImage:image source:source]; |
| |
| _currentDragOperation = [_webView draggingEntered:_draggingInfo.get()]; |
| [_webView waitForNextPresentationUpdate]; |
| |
| while (_progress != 1) { |
| _progress = std::min<double>(1, _progress + dragUpdateProgressIncrement); |
| [_draggingInfo setDraggingLocation:[self locationInViewForCurrentProgress]]; |
| _currentDragOperation = [_webView draggingUpdated:_draggingInfo.get()]; |
| [_webView waitForNextPresentationUpdate]; |
| } |
| |
| [_draggingInfo setDraggingLocation:_endLocationInWindow]; |
| |
| if (_willEndDraggingHandler) |
| _willEndDraggingHandler(); |
| |
| if (_currentDragOperation != NSDragOperationNone && [_webView prepareForDragOperation:_draggingInfo.get()]) { |
| _doneWaitingForDrop = false; |
| [_webView performDragOperation:_draggingInfo.get()]; |
| } else if (_currentDragOperation == NSDragOperationNone) |
| [_webView draggingExited:_draggingInfo.get()]; |
| [_webView waitForNextPresentationUpdate]; |
| |
| if (!self.externalDragPasteboard) { |
| [_webView draggedImage:[_draggingInfo draggedImage] endedAt:_endLocationInWindow operation:_currentDragOperation]; |
| [_webView waitForNextPresentationUpdate]; |
| } |
| } |
| |
| - (void)initializeDraggingInfo:(NSPasteboard *)pasteboard dragImage:(NSImage *)image source:(id)source |
| { |
| _draggingInfo = adoptNS([[TestDraggingInfo alloc] initWithDragAndDropSimulator:self]); |
| [_draggingInfo setDraggedImage:image]; |
| [_draggingInfo setDraggingPasteboard:pasteboard]; |
| [_draggingInfo setDraggingSource:source]; |
| [_draggingInfo setDraggingLocation:[self locationInViewForCurrentProgress]]; |
| [_draggingInfo setDraggingSourceOperationMask:NSDragOperationEvery]; |
| [_draggingInfo setNumberOfValidItemsForDrop:pasteboard.pasteboardItems.count]; |
| } |
| |
| - (NSArray<_WKAttachment *> *)insertedAttachments |
| { |
| return _insertedAttachments.get(); |
| } |
| |
| - (NSArray<_WKAttachment *> *)removedAttachments |
| { |
| return _removedAttachments.get(); |
| } |
| |
| - (TestWKWebView *)webView |
| { |
| return _webView.get(); |
| } |
| |
| - (void)setExternalDragPasteboard:(NSPasteboard *)externalDragPasteboard |
| { |
| _externalDragPasteboard = externalDragPasteboard; |
| } |
| |
| - (NSPasteboard *)externalDragPasteboard |
| { |
| return _externalDragPasteboard.get(); |
| } |
| |
| - (void)setExternalDragImage:(NSImage *)externalDragImage |
| { |
| _externalDragImage = externalDragImage; |
| } |
| |
| - (NSImage *)externalDragImage |
| { |
| return _externalDragImage.get(); |
| } |
| |
| - (NSDraggingSession *)draggingSession |
| { |
| return _draggingSession.get(); |
| } |
| |
| - (id <NSDraggingInfo>)draggingInfo |
| { |
| return _draggingInfo.get(); |
| } |
| |
| - (dispatch_block_t)willEndDraggingHandler |
| { |
| return _willEndDraggingHandler.get(); |
| } |
| |
| - (void)setWillEndDraggingHandler:(dispatch_block_t)willEndDraggingHandler |
| { |
| _willEndDraggingHandler = makeBlockPtr(willEndDraggingHandler); |
| } |
| |
| - (NSArray<NSURL *> *)externalPromisedFiles |
| { |
| return _externalPromisedFiles.get(); |
| } |
| |
| - (void)clearExternalDragInformation |
| { |
| _externalPromisedFiles = nil; |
| _externalDragImage = nil; |
| _externalDragPasteboard = nil; |
| } |
| |
| static BOOL getFilePathsAndTypeIdentifiers(NSArray<NSURL *> *fileURLs, NSArray<NSString *> **outFilePaths, NSArray<NSString *> **outTypeIdentifiers) |
| { |
| NSMutableArray *filePaths = [NSMutableArray arrayWithCapacity:fileURLs.count]; |
| NSMutableArray *typeIdentifiers = [NSMutableArray arrayWithCapacity:fileURLs.count]; |
| for (NSURL *url in fileURLs) { |
| NSString *typeIdentifier = nil; |
| NSError *error = nil; |
| BOOL foundUTI = [url getResourceValue:&typeIdentifier forKey:NSURLTypeIdentifierKey error:&error]; |
| if (!foundUTI || error) { |
| [NSException raise:@"DragAndDropSimulator" format:@"Failed to get UTI for promised file: %@ with error: %@", url, error]; |
| continue; |
| } |
| [typeIdentifiers addObject:typeIdentifier]; |
| [filePaths addObject:url.path]; |
| } |
| |
| if (fileURLs.count != filePaths.count) |
| return NO; |
| |
| if (outTypeIdentifiers) |
| *outTypeIdentifiers = typeIdentifiers; |
| |
| if (outFilePaths) |
| *outFilePaths = filePaths; |
| |
| return YES; |
| } |
| |
| - (void)writePromisedFiles:(NSArray<NSURL *> *)fileURLs |
| { |
| NSArray *paths = nil; |
| NSArray *types = nil; |
| if (!getFilePathsAndTypeIdentifiers(fileURLs, &paths, &types)) |
| return; |
| |
| NSMutableArray *names = [NSMutableArray arrayWithCapacity:paths.count]; |
| for (NSString *path in paths) |
| [names addObject:path.lastPathComponent]; |
| |
| _externalPromisedFiles = fileURLs; |
| _externalDragPasteboard = [NSPasteboard pasteboardWithUniqueName]; |
| [_externalDragPasteboard declareTypes:@[NSFilesPromisePboardType, NSFilenamesPboardType] owner:nil]; |
| [_externalDragPasteboard setPropertyList:types forType:NSFilesPromisePboardType]; |
| [_externalDragPasteboard setPropertyList:names forType:NSFilenamesPboardType]; |
| } |
| |
| - (void)writeFiles:(NSArray<NSURL *> *)fileURLs |
| { |
| NSArray *paths = nil; |
| if (!getFilePathsAndTypeIdentifiers(fileURLs, &paths, nil)) |
| return; |
| |
| _externalDragPasteboard = [NSPasteboard pasteboardWithName:NSDragPboard]; |
| [_externalDragPasteboard declareTypes:@[NSFilenamesPboardType] owner:nil]; |
| [_externalDragPasteboard setPropertyList:paths forType:NSFilenamesPboardType]; |
| } |
| |
| - (NSArray<NSURL *> *)receivePromisedFiles |
| { |
| auto destinationURLs = adoptNS([NSMutableArray new]); |
| for (NSFilePromiseProvider *provider in _filePromiseProviders.get()) { |
| if (!provider.delegate) |
| continue; |
| |
| int suffix = 1; |
| NSString *baseFileName = [provider.delegate filePromiseProvider:provider fileNameForType:provider.fileType]; |
| NSString *uniqueFileName = baseFileName; |
| while ([[NSFileManager defaultManager] fileExistsAtPath:[NSTemporaryDirectory() stringByAppendingPathComponent:uniqueFileName]]) |
| uniqueFileName = [NSString stringWithFormat:@"%@ %d", baseFileName, ++suffix]; |
| |
| NSURL *destinationURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:uniqueFileName]]; |
| __block bool done = false; |
| [provider.delegate filePromiseProvider:provider writePromiseToURL:destinationURL completionHandler:^(NSError *) { |
| done = true; |
| }]; |
| TestWebKitAPI::Util::run(&done); |
| [destinationURLs addObject:destinationURL]; |
| [_filePromiseDestinationURLs addObject:destinationURL]; |
| } |
| return destinationURLs.autorelease(); |
| } |
| |
| - (void)endDataTransfer |
| { |
| } |
| |
| #pragma mark - WKUIDelegatePrivate |
| |
| - (void)_webView:(WKWebView *)webView didInsertAttachment:(_WKAttachment *)attachment withSource:(NSString *)source |
| { |
| [_insertedAttachments addObject:attachment]; |
| } |
| |
| - (void)_webView:(WKWebView *)webView didRemoveAttachment:(_WKAttachment *)attachment |
| { |
| [_removedAttachments addObject:attachment]; |
| } |
| |
| - (void)_webView:(WKWebView *)webView didPerformDragOperation:(BOOL)handled |
| { |
| _doneWaitingForDrop = true; |
| } |
| |
| - (WKDragDestinationAction)_webView:(WKWebView *)webView dragDestinationActionMaskForDraggingInfo:(id)draggingInfo |
| { |
| return self.dragDestinationAction; |
| } |
| |
| @end |
| |
| #endif // ENABLE(DRAG_SUPPORT) && PLATFORM(MAC) |