[iOS DnD] Upstream iOS drag and drop implementation into OpenSource WebKit
[WebKit.git] / Source / WebCore / platform / mac / DragDataMac.mm
1 /*
2  * Copyright (C) 2007 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "config.h"
27 #import "DragData.h"
28
29 #if ENABLE(DRAG_SUPPORT)
30 #import "MIMETypeRegistry.h"
31 #import "NotImplemented.h"
32 #import "Pasteboard.h"
33 #import "PasteboardStrategy.h"
34 #import "PlatformPasteboard.h"
35 #import "PlatformStrategies.h"
36 #import "WebCoreNSURLExtras.h"
37
38 #if PLATFORM(IOS)
39 #import <MobileCoreServices/MobileCoreServices.h>
40 #endif
41
42 namespace WebCore {
43
44 static inline String rtfPasteboardType()
45 {
46 #if PLATFORM(IOS)
47     return String(kUTTypeRTF);
48 #else
49     return String(NSRTFPboardType);
50 #endif
51 }
52
53 static inline String rtfdPasteboardType()
54 {
55 #if PLATFORM(IOS)
56     return String(kUTTypeFlatRTFD);
57 #else
58     return String(NSRTFDPboardType);
59 #endif
60 }
61
62 static inline String stringPasteboardType()
63 {
64 #if PLATFORM(IOS)
65     return String(kUTTypeText);
66 #else
67     return String(NSStringPboardType);
68 #endif
69 }
70
71 static inline String urlPasteboardType()
72 {
73 #if PLATFORM(IOS)
74     return String(kUTTypeURL);
75 #else
76     return String(NSURLPboardType);
77 #endif
78 }
79
80 static inline String htmlPasteboardType()
81 {
82 #if PLATFORM(IOS)
83     return String(kUTTypeHTML);
84 #else
85     return String(NSHTMLPboardType);
86 #endif
87 }
88
89 static inline String colorPasteboardType()
90 {
91 #if PLATFORM(IOS)
92     return "com.apple.uikit.color";
93 #else
94     return String(NSColorPboardType);
95 #endif
96 }
97
98 static inline String pdfPasteboardType()
99 {
100 #if PLATFORM(IOS)
101     return String(kUTTypePDF);
102 #else
103     return String(NSPDFPboardType);
104 #endif
105 }
106
107 static inline String tiffPasteboardType()
108 {
109 #if PLATFORM(IOS)
110     return String(kUTTypeTIFF);
111 #else
112     return String(NSTIFFPboardType);
113 #endif
114 }
115
116 DragData::DragData(DragDataRef data, const IntPoint& clientPosition, const IntPoint& globalPosition, DragOperation sourceOperationMask, DragApplicationFlags flags, DragDestinationAction destinationAction)
117     : m_clientPosition(clientPosition)
118     , m_globalPosition(globalPosition)
119     , m_platformDragData(data)
120     , m_draggingSourceOperationMask(sourceOperationMask)
121     , m_applicationFlags(flags)
122     , m_dragDestinationAction(destinationAction)
123 #if PLATFORM(MAC)
124     , m_pasteboardName([[m_platformDragData draggingPasteboard] name])
125 #else
126     , m_pasteboardName("data interaction pasteboard")
127 #endif
128 {
129 }
130
131 DragData::DragData(const String& dragStorageName, const IntPoint& clientPosition, const IntPoint& globalPosition, DragOperation sourceOperationMask, DragApplicationFlags flags, DragDestinationAction destinationAction)
132     : m_clientPosition(clientPosition)
133     , m_globalPosition(globalPosition)
134     , m_platformDragData(0)
135     , m_draggingSourceOperationMask(sourceOperationMask)
136     , m_applicationFlags(flags)
137     , m_dragDestinationAction(destinationAction)
138     , m_pasteboardName(dragStorageName)
139 {
140 }
141
142 bool DragData::containsURLTypeIdentifier() const
143 {
144     Vector<String> types;
145     platformStrategies()->pasteboardStrategy()->getTypes(types, m_pasteboardName);
146     return types.contains(urlPasteboardType());
147 }
148     
149 bool DragData::canSmartReplace() const
150 {
151     return Pasteboard(m_pasteboardName).canSmartReplace();
152 }
153
154 bool DragData::containsColor() const
155 {
156     Vector<String> types;
157     platformStrategies()->pasteboardStrategy()->getTypes(types, m_pasteboardName);
158     return types.contains(colorPasteboardType());
159 }
160
161 bool DragData::containsFiles() const
162 {
163     Vector<String> types;
164     platformStrategies()->pasteboardStrategy()->getTypes(types, m_pasteboardName);
165     for (auto& type : types) {
166 #if PLATFORM(MAC)
167         if (type == String(NSFilesPromisePboardType) || type == String(NSFilenamesPboardType))
168             return true;
169 #else
170         if (UTTypeConformsTo(type.createCFString().autorelease(), kUTTypeContent))
171             return true;
172 #endif
173     }
174     return false;
175 }
176
177 unsigned DragData::numberOfFiles() const
178 {
179     return platformStrategies()->pasteboardStrategy()->getNumberOfFiles(m_pasteboardName);
180 }
181
182 void DragData::asFilenames(Vector<String>& result) const
183 {
184 #if PLATFORM(MAC)
185     platformStrategies()->pasteboardStrategy()->getPathnamesForType(result, String(NSFilenamesPboardType), m_pasteboardName);
186 #endif
187     if (!result.size())
188         result = fileNames();
189 }
190
191 bool DragData::containsPlainText() const
192 {
193     Vector<String> types;
194     platformStrategies()->pasteboardStrategy()->getTypes(types, m_pasteboardName);
195
196     return types.contains(stringPasteboardType())
197         || types.contains(rtfdPasteboardType())
198         || types.contains(rtfPasteboardType())
199 #if PLATFORM(MAC)
200         || types.contains(String(NSFilenamesPboardType))
201 #endif
202         || platformStrategies()->pasteboardStrategy()->stringForType(urlPasteboardType(), m_pasteboardName).length();
203 }
204
205 String DragData::asPlainText() const
206 {
207     Pasteboard pasteboard(m_pasteboardName);
208     PasteboardPlainText text;
209     pasteboard.read(text);
210     String string = text.text;
211
212     // FIXME: It's not clear this is 100% correct since we know -[NSURL URLWithString:] does not handle
213     // all the same cases we handle well in the URL code for creating an NSURL.
214     if (text.isURL)
215         return userVisibleString([NSURL URLWithString:string]);
216
217     // FIXME: WTF should offer a non-Mac-specific way to convert string to precomposed form so we can do it for all platforms.
218     return [(NSString *)string precomposedStringWithCanonicalMapping];
219 }
220
221 Color DragData::asColor() const
222 {
223     return platformStrategies()->pasteboardStrategy()->color(m_pasteboardName);
224 }
225
226 bool DragData::containsCompatibleContent(DraggingPurpose purpose) const
227 {
228     if (purpose == DraggingPurpose::ForFileUpload)
229         return containsFiles();
230
231     Vector<String> types;
232     platformStrategies()->pasteboardStrategy()->getTypes(types, m_pasteboardName);
233     return types.contains(String(WebArchivePboardType))
234         || types.contains(htmlPasteboardType())
235 #if PLATFORM(MAC)
236         || types.contains(String(NSFilenamesPboardType))
237         || types.contains(String(NSFilesPromisePboardType))
238 #endif
239         || types.contains(tiffPasteboardType())
240         || types.contains(pdfPasteboardType())
241         || types.contains(urlPasteboardType())
242         || types.contains(rtfdPasteboardType())
243         || types.contains(rtfPasteboardType())
244         || types.contains(String(kUTTypeUTF8PlainText))
245         || types.contains(stringPasteboardType())
246         || types.contains(colorPasteboardType())
247         || types.contains(String(kUTTypeJPEG))
248         || types.contains(String(kUTTypePNG));
249 }
250
251 bool DragData::containsPromise() const
252 {
253     Vector<String> files;
254 #if PLATFORM(MAC)
255     platformStrategies()->pasteboardStrategy()->getPathnamesForType(files, String(NSFilesPromisePboardType), m_pasteboardName);
256 #endif
257     return files.size() == 1;
258 }
259
260 bool DragData::containsURL(FilenameConversionPolicy filenamePolicy) const
261 {
262 #if PLATFORM(IOS)
263     UNUSED_PARAM(filenamePolicy);
264     Vector<String> types;
265     platformStrategies()->pasteboardStrategy()->getTypes(types, m_pasteboardName);
266     if (!types.contains(urlPasteboardType()))
267         return false;
268
269     auto urlString = platformStrategies()->pasteboardStrategy()->stringForType(urlPasteboardType(), m_pasteboardName);
270     if (urlString.isEmpty()) {
271         // On iOS, we don't get access to the contents of UIItemProviders until we perform the drag operation.
272         // Thus, we consider DragData to contain an URL if it contains the `public.url` UTI type. Later down the
273         // road, when we perform the drag operation, we can then check if the URL's protocol is http or https,
274         // and if it isn't, we bail out of page navigation.
275         return true;
276     }
277
278     URL webcoreURL = [NSURL URLWithString:urlString];
279     return webcoreURL.protocolIs("http") || webcoreURL.protocolIs("https");
280 #else
281     return !asURL(filenamePolicy).isEmpty();
282 #endif
283 }
284
285 String DragData::asURL(FilenameConversionPolicy, String* title) const
286 {
287     // FIXME: Use filenamePolicy.
288
289     if (title) {
290 #if PLATFORM(MAC)
291         String URLTitleString = platformStrategies()->pasteboardStrategy()->stringForType(String(WebURLNamePboardType), m_pasteboardName);
292         if (!URLTitleString.isEmpty())
293             *title = URLTitleString;
294 #endif
295     }
296     
297     Vector<String> types;
298     platformStrategies()->pasteboardStrategy()->getTypes(types, m_pasteboardName);
299
300     if (types.contains(urlPasteboardType())) {
301         NSURL *URLFromPasteboard = [NSURL URLWithString:platformStrategies()->pasteboardStrategy()->stringForType(urlPasteboardType(), m_pasteboardName)];
302         NSString *scheme = [URLFromPasteboard scheme];
303         // Cannot drop other schemes unless <rdar://problem/10562662> and <rdar://problem/11187315> are fixed.
304         if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"])
305             return [URLByCanonicalizingURL(URLFromPasteboard) absoluteString];
306     }
307     
308     if (types.contains(stringPasteboardType())) {
309         NSURL *URLFromPasteboard = [NSURL URLWithString:platformStrategies()->pasteboardStrategy()->stringForType(stringPasteboardType(), m_pasteboardName)];
310         NSString *scheme = [URLFromPasteboard scheme];
311         // Pasteboard content is not trusted, because JavaScript code can modify it. We can sanitize it for URLs and other typed content, but not for strings.
312         // The result of this function is used to initiate navigation, so we shouldn't allow arbitrary file URLs.
313         // FIXME: Should we allow only http family schemes, or anything non-local?
314         if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"])
315             return [URLByCanonicalizingURL(URLFromPasteboard) absoluteString];
316     }
317     
318 #if PLATFORM(MAC)
319     if (types.contains(String(NSFilenamesPboardType))) {
320         Vector<String> files;
321         platformStrategies()->pasteboardStrategy()->getPathnamesForType(files, String(NSFilenamesPboardType), m_pasteboardName);
322         if (files.size() == 1) {
323             BOOL isDirectory;
324             if ([[NSFileManager defaultManager] fileExistsAtPath:files[0] isDirectory:&isDirectory] && isDirectory)
325                 return String();
326             return [URLByCanonicalizingURL([NSURL fileURLWithPath:files[0]]) absoluteString];
327         }
328     }
329
330     if (types.contains(String(NSFilesPromisePboardType)) && fileNames().size() == 1)
331         return [URLByCanonicalizingURL([NSURL fileURLWithPath:fileNames()[0]]) absoluteString];
332 #endif
333
334     return String();        
335 }
336
337 } // namespace WebCore
338
339 #endif // ENABLE(DRAG_SUPPORT)