[Mac] Teach WebCore::Pasteboard about file promise drags
[WebKit-https.git] / Tools / DumpRenderTree / mac / DumpRenderTreeDraggingInfo.mm
1 /*
2  * Copyright (C) 2005-2018 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  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #import "config.h"
30 #import "DumpRenderTreeDraggingInfo.h"
31
32 #if !PLATFORM(IOS)
33
34 #import "DumpRenderTree.h"
35 #import "DumpRenderTreeFileDraggingSource.h"
36 #import "DumpRenderTreePasteboard.h"
37 #import "EventSendingController.h"
38 #import <WebKit/WebKit.h>
39 #import <wtf/RetainPtr.h>
40
41 @interface NSDraggingItem ()
42 - (void)setItem:(id)item;
43 @end
44
45 @interface DumpRenderTreeFilePromiseReceiver : NSFilePromiseReceiver {
46     RetainPtr<NSArray<NSString *>> _promisedUTIs;
47     RetainPtr<NSMutableArray<NSURL *>> _destinationURLs;
48     DumpRenderTreeFileDraggingSource *_draggingSource;
49 }
50
51 - (instancetype)initWithPromisedUTIs:(NSArray<NSString *> *)promisedUTIs;
52
53 @property (nonatomic, retain) DumpRenderTreeFileDraggingSource *draggingSource;
54
55 @end
56
57 @implementation DumpRenderTreeFilePromiseReceiver
58
59 @synthesize draggingSource=_draggingSource;
60
61 - (instancetype)initWithPromisedUTIs:(NSArray<NSString *> *)promisedUTIs
62 {
63     if (!(self = [super init]))
64         return nil;
65
66     _promisedUTIs = adoptNS([promisedUTIs copy]);
67     _destinationURLs = adoptNS([NSMutableArray new]);
68     return self;
69 }
70
71 - (NSArray<NSString *> *)fileTypes
72 {
73     return _promisedUTIs.get();
74 }
75
76 - (NSArray<NSString *> *)fileNames
77 {
78     NSMutableArray *fileNames = [NSMutableArray arrayWithCapacity:[_destinationURLs count]];
79     for (NSURL *url in _destinationURLs.get())
80         [fileNames addObject:url.lastPathComponent];
81     return fileNames;
82 }
83
84 - (void)dealloc
85 {
86     // WebKit does not delete promised files it receives into NSTemporaryDirectory() (it should!),
87     // so we need to. Failing to do so could result in unpredictable file names in a subsequent test
88     // that promises a file with the same name as one of these destination URLs.
89
90     for (NSURL *destinationURL in _destinationURLs.get()) {
91         assert([destinationURL.path hasPrefix:NSTemporaryDirectory()]);
92         [NSFileManager.defaultManager removeItemAtURL:destinationURL error:nil];
93     }
94
95     [_draggingSource release];
96     [super dealloc];
97 }
98
99 static NSURL *copyFile(NSURL *sourceURL, NSURL *destinationDirectory, NSError *&error)
100 {
101     // Emulate how CFPasteboard finds unique destination file names by inserting " 2", " 3", and so
102     // on between the file name's base and extension until a new file is successfully created in
103     // the destination directory.
104
105     NSUInteger number = 2;
106     NSString *fileName = sourceURL.lastPathComponent;
107     NSURL *destinationURL = [NSURL fileURLWithPath:fileName relativeToURL:destinationDirectory];
108     while (![NSFileManager.defaultManager copyItemAtURL:sourceURL toURL:destinationURL error:&error]) {
109         if (error.domain != NSCocoaErrorDomain || error.code != NSFileWriteFileExistsError)
110             return nil;
111
112         NSString *newFileName = [NSString stringWithFormat:@"%@ %lu.%@", fileName.stringByDeletingPathExtension, (unsigned long)number++, fileName.pathExtension];
113         destinationURL = [NSURL fileURLWithPath:newFileName relativeToURL:destinationDirectory];
114         error = nil;
115     }
116
117     return destinationURL;
118 }
119
120 - (void)receivePromisedFilesAtDestination:(NSURL *)destinationDirectory options:(NSDictionary *)options operationQueue:(NSOperationQueue *)operationQueue reader:(void (^)(NSURL *fileURL, NSError * __nullable errorOrNil))reader
121 {
122     // Layout tests need files to be received in a predictable order, so execute operations in serial.
123     operationQueue.maxConcurrentOperationCount = 1;
124
125     NSArray<NSURL *> *sourceURLs = _draggingSource.promisedFileURLs;
126     for (NSURL *sourceURL in sourceURLs) {
127         [operationQueue addOperationWithBlock:^{
128             NSError *error = nil;
129             NSURL *destinationURL = copyFile(sourceURL, destinationDirectory, error);
130             if (destinationURL) {
131                 dispatch_async(dispatch_get_main_queue(), ^{
132                     [_destinationURLs addObject:destinationURL];
133                 });
134             }
135
136             reader(destinationURL, error);
137         }];
138     }
139 }
140
141 @end
142
143 @implementation DumpRenderTreeDraggingInfo
144
145 - (id)initWithImage:(NSImage *)anImage offset:(NSSize)o pasteboard:(NSPasteboard *)pboard source:(id)source
146 {
147     draggedImage = [anImage retain];
148     draggingPasteboard = [pboard retain];
149     draggingSource = [source retain];
150     offset = o;
151     
152     return [super init];
153 }
154
155 - (void)dealloc
156 {
157     [draggedImage release];
158     [draggingPasteboard release];
159     [draggingSource release];
160     [super dealloc];
161 }
162
163 - (NSWindow *)draggingDestinationWindow 
164 {
165     return [[mainFrame webView] window];
166 }
167
168 - (NSDragOperation)draggingSourceOperationMask 
169 {
170     return [draggingSource draggingSourceOperationMaskForLocal:YES];
171 }
172
173 - (NSPoint)draggingLocation
174
175     return lastMousePosition; 
176 }
177
178 - (NSPoint)draggedImageLocation 
179 {
180     return NSMakePoint(lastMousePosition.x + offset.width, lastMousePosition.y + offset.height);
181 }
182
183 - (NSImage *)draggedImage
184 {
185     return draggedImage;
186 }
187
188 - (NSPasteboard *)draggingPasteboard
189 {
190     return draggingPasteboard;
191 }
192
193 - (id)draggingSource
194 {
195     return draggingSource;
196 }
197
198 - (int)draggingSequenceNumber
199 {
200     NSLog(@"DumpRenderTree doesn't support draggingSequenceNumber");
201     return 0;
202 }
203
204 - (void)slideDraggedImageTo:(NSPoint)screenPoint
205 {
206     NSLog(@"DumpRenderTree doesn't support slideDraggedImageTo:");
207 }
208
209 - (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
210 {
211     NSLog(@"DumpRenderTree doesn't support namesOfPromisedFilesDroppedAtDestination:");
212     return nil;
213 }
214
215 - (NSDraggingFormation)draggingFormation
216 {
217     return NSDraggingFormationDefault;
218 }
219
220 - (void)setDraggingFormation:(NSDraggingFormation)formation
221 {
222     // Ignored.
223 }
224
225 - (BOOL)animatesToDestination
226 {
227     return NO;
228 }
229
230 - (void)setAnimatesToDestination:(BOOL)flag
231 {
232     // Ignored.
233 }
234
235 - (NSInteger)numberOfValidItemsForDrop
236 {
237     return 1;
238 }
239
240 - (void)setNumberOfValidItemsForDrop:(NSInteger)number
241 {
242     // Ignored.
243 }
244
245 static NSMutableArray<NSFilePromiseReceiver *> *allFilePromiseReceivers()
246 {
247     static NSMutableArray<NSFilePromiseReceiver *> *allReceivers = [[NSMutableArray alloc] init];
248     return allReceivers;
249 }
250
251 + (void)clearAllFilePromiseReceivers
252 {
253     [allFilePromiseReceivers() removeAllObjects];
254 }
255
256 - (void)enumerateDraggingItemsWithOptions:(NSEnumerationOptions)enumOptions forView:(NSView *)view classes:(NSArray *)classArray searchOptions:(NSDictionary *)searchOptions usingBlock:(void (^)(NSDraggingItem *draggingItem, NSInteger idx, BOOL *stop))block
257 {
258     assert(!enumOptions);
259     assert(!searchOptions.count);
260
261     BOOL stop = NO;
262     for (Class classObject in classArray) {
263         if (classObject != NSFilePromiseReceiver.class)
264             continue;
265
266         id promisedUTIs = [draggingPasteboard propertyListForType:NSFilesPromisePboardType];
267         if (![promisedUTIs isKindOfClass:NSArray.class])
268             return;
269
270         for (id object in promisedUTIs) {
271             if (![object isKindOfClass:NSString.class])
272                 return;
273         }
274
275         auto receiver = adoptNS([[DumpRenderTreeFilePromiseReceiver alloc] initWithPromisedUTIs:promisedUTIs]);
276         [receiver setDraggingSource:draggingSource];
277         [allFilePromiseReceivers() addObject:receiver.get()];
278
279         auto item = adoptNS([NSDraggingItem new]);
280         [item setItem:receiver.get()];
281
282         block(item.get(), 0, &stop);
283         if (stop)
284             return;
285     }
286 }
287
288 -(NSSpringLoadingHighlight)springLoadingHighlight
289 {
290     return NSSpringLoadingHighlightNone;
291 }
292
293 - (void)resetSpringLoading
294 {
295 }
296
297 @end
298
299 #endif // !PLATFORM(IOS)