[iOS DnD] Support DataTransfer.getData and DataTransfer.setData when dragging or...
[WebKit-https.git] / Source / WebCore / platform / ios / PasteboardIOS.mm
1 /*
2  * Copyright (C) 2007, 2008, 2012, 2013 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'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16  * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24
25 #import "config.h"
26 #import "Pasteboard.h"
27
28 #import "CachedImage.h"
29 #import "Document.h"
30 #import "DocumentFragment.h"
31 #import "DocumentLoader.h"
32 #import "DragData.h"
33 #import "Editing.h"
34 #import "Editor.h"
35 #import "EditorClient.h"
36 #import "Frame.h"
37 #import "FrameLoader.h"
38 #import "FrameLoaderClient.h"
39 #import "HTMLElement.h"
40 #import "HTMLParserIdioms.h"
41 #import "Image.h"
42 #import "LegacyWebArchive.h"
43 #import "NotImplemented.h"
44 #import "PasteboardStrategy.h"
45 #import "PlatformStrategies.h"
46 #import "RenderImage.h"
47 #import "RuntimeApplicationChecks.h"
48 #import "SharedBuffer.h"
49 #import "Text.h"
50 #import "URL.h"
51 #import "UTIUtilities.h"
52 #import "WebNSAttributedStringExtras.h"
53 #import "markup.h"
54 #import <MobileCoreServices/MobileCoreServices.h>
55
56 @interface NSAttributedString (NSAttributedStringKitAdditions)
57 - (id)initWithRTF:(NSData *)data documentAttributes:(NSDictionary **)dict;
58 - (id)initWithRTFD:(NSData *)data documentAttributes:(NSDictionary **)dict;
59 - (NSData *)RTFFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict;
60 - (NSData *)RTFDFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict;
61 - (BOOL)containsAttachments;
62 @end
63
64 namespace WebCore {
65
66 #if ENABLE(DRAG_SUPPORT)
67
68 Pasteboard::Pasteboard(const String& pasteboardName)
69     : m_pasteboardName(pasteboardName)
70     , m_changeCount(platformStrategies()->pasteboardStrategy()->changeCount(pasteboardName))
71 {
72 }
73
74 void Pasteboard::setDragImage(DragImage, const IntPoint&)
75 {
76     notImplemented();
77 }
78
79 std::unique_ptr<Pasteboard> Pasteboard::createForDragAndDrop()
80 {
81     return std::make_unique<Pasteboard>("data interaction pasteboard");
82 }
83
84 std::unique_ptr<Pasteboard> Pasteboard::createForDragAndDrop(const DragData& dragData)
85 {
86     return std::make_unique<Pasteboard>(dragData.pasteboardName());
87 }
88
89 #endif
90
91 static long changeCountForPasteboard(const String& pasteboardName = { })
92 {
93     return platformStrategies()->pasteboardStrategy()->changeCount(pasteboardName);
94 }
95
96 // FIXME: Does this need to be declared in the header file?
97 WEBCORE_EXPORT NSString *WebArchivePboardType = @"Apple Web Archive pasteboard type";
98
99 // Making this non-inline so that WebKit 2's decoding doesn't have to include SharedBuffer.h.
100 PasteboardWebContent::PasteboardWebContent()
101 {
102 }
103
104 PasteboardWebContent::~PasteboardWebContent()
105 {
106 }
107     
108 // Making this non-inline so that WebKit 2's decoding doesn't have to include Image.h.
109 PasteboardImage::PasteboardImage()
110 {
111 }
112
113 PasteboardImage::~PasteboardImage()
114 {
115 }
116
117 Pasteboard::Pasteboard()
118     : m_changeCount(0)
119 {
120 }
121
122 Pasteboard::Pasteboard(long changeCount)
123     : m_changeCount(changeCount)
124 {
125 }
126
127 void Pasteboard::writeMarkup(const String&)
128 {
129 }
130
131 std::unique_ptr<Pasteboard> Pasteboard::createForCopyAndPaste()
132 {
133     return std::make_unique<Pasteboard>(changeCountForPasteboard());
134 }
135
136 std::unique_ptr<Pasteboard> Pasteboard::createPrivate()
137 {
138     return std::make_unique<Pasteboard>(changeCountForPasteboard());
139 }
140
141 void Pasteboard::write(const PasteboardWebContent& content)
142 {
143     platformStrategies()->pasteboardStrategy()->writeToPasteboard(content, m_pasteboardName);
144 }
145
146 String Pasteboard::resourceMIMEType(NSString *mimeType)
147 {
148     return UTIFromMIMEType(mimeType);
149 }
150
151 void Pasteboard::write(const PasteboardImage& pasteboardImage)
152 {
153     platformStrategies()->pasteboardStrategy()->writeToPasteboard(pasteboardImage, m_pasteboardName);
154 }
155
156 void Pasteboard::writePlainText(const String& text, SmartReplaceOption)
157 {
158     // FIXME: We vend "public.text" here for backwards compatibility with pre-iOS 11 apps. In the future, we should stop vending this UTI,
159     // and instead set data for concrete plain text types. See <https://bugs.webkit.org/show_bug.cgi?id=173317>.
160     platformStrategies()->pasteboardStrategy()->writeToPasteboard(kUTTypeText, text, m_pasteboardName);
161 }
162
163 void Pasteboard::write(const PasteboardURL& pasteboardURL)
164 {
165     platformStrategies()->pasteboardStrategy()->writeToPasteboard(pasteboardURL, m_pasteboardName);
166 }
167
168 void Pasteboard::writeTrustworthyWebURLsPboardType(const PasteboardURL&)
169 {
170     // A trustworthy URL pasteboard type needs to be decided on
171     // before we allow calls to this function. A page data transfer
172     // should not use the same pasteboard type as this function for
173     // URLs.
174     ASSERT_NOT_REACHED();
175 }
176
177 void Pasteboard::writePasteboard(const Pasteboard&)
178 {
179 }
180
181 bool Pasteboard::canSmartReplace()
182 {
183     return false;
184 }
185
186 void Pasteboard::read(PasteboardPlainText& text)
187 {
188     PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
189     text.text = strategy.readStringFromPasteboard(0, kUTTypeURL, m_pasteboardName);
190     if (!text.text.isNull() && !text.text.isEmpty()) {
191         text.isURL = true;
192         return;
193     }
194
195     text.text = strategy.readStringFromPasteboard(0, kUTTypeText, m_pasteboardName);
196     if (text.text.isEmpty())
197         text.text = strategy.readStringFromPasteboard(0, kUTTypePlainText, m_pasteboardName);
198
199     text.isURL = false;
200 }
201
202 static NSArray* supportedImageTypes()
203 {
204     return @[(id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF];
205 }
206
207 static bool readPasteboardWebContentDataForType(PasteboardWebContentReader& reader, PasteboardStrategy& strategy, NSString *type, int itemIndex, const String& pasteboardName)
208 {
209     if ([type isEqualToString:WebArchivePboardType]) {
210         RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, WebArchivePboardType, pasteboardName);
211         return reader.readWebArchive(buffer.get());
212     }
213
214     if ([type isEqualToString:(NSString *)kUTTypeHTML]) {
215         String htmlString = strategy.readStringFromPasteboard(itemIndex, kUTTypeHTML, pasteboardName);
216         return !htmlString.isNull() && reader.readHTML(htmlString);
217     }
218
219     if ([type isEqualToString:(NSString *)kUTTypeFlatRTFD]) {
220         RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, kUTTypeFlatRTFD, pasteboardName);
221         return buffer && reader.readRTFD(*buffer);
222     }
223
224     if ([type isEqualToString:(NSString *)kUTTypeRTF]) {
225         RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, kUTTypeRTF, pasteboardName);
226         return buffer && reader.readRTF(*buffer);
227     }
228
229     if ([supportedImageTypes() containsObject:type]) {
230         RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(itemIndex, type, pasteboardName);
231         return buffer && reader.readImage(buffer.releaseNonNull(), type);
232     }
233
234     if ([type isEqualToString:(NSString *)kUTTypeURL]) {
235         String title;
236         URL url = strategy.readURLFromPasteboard(itemIndex, kUTTypeURL, pasteboardName, title);
237         return !url.isNull() && reader.readURL(url, title);
238     }
239
240     if (UTTypeConformsTo((CFStringRef)type, kUTTypePlainText)) {
241         String string = strategy.readStringFromPasteboard(itemIndex, kUTTypePlainText, pasteboardName);
242         return !string.isNull() && reader.readPlainText(string);
243     }
244
245     if (UTTypeConformsTo((CFStringRef)type, kUTTypeText)) {
246         String string = strategy.readStringFromPasteboard(itemIndex, kUTTypeText, pasteboardName);
247         return !string.isNull() && reader.readPlainText(string);
248     }
249
250     return false;
251 }
252
253 void Pasteboard::read(PasteboardWebContentReader& reader)
254 {
255     if (respectsUTIFidelities()) {
256         readRespectingUTIFidelities(reader);
257         return;
258     }
259
260     PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
261
262     int numberOfItems = strategy.getPasteboardItemsCount(m_pasteboardName);
263
264     if (!numberOfItems)
265         return;
266
267     NSArray *types = supportedWebContentPasteboardTypes();
268     int numberOfTypes = [types count];
269
270     for (int i = 0; i < numberOfItems; i++) {
271         for (int typeIndex = 0; typeIndex < numberOfTypes; typeIndex++) {
272             if (readPasteboardWebContentDataForType(reader, strategy, [types objectAtIndex:typeIndex], i, m_pasteboardName))
273                 break;
274         }
275     }
276 }
277
278 bool Pasteboard::respectsUTIFidelities() const
279 {
280     // For now, data interaction is the only feature that uses item-provider-based pasteboard representations.
281     // In the future, we may need to consult the client layer to determine whether or not the pasteboard supports
282     // item types ranked by fidelity.
283     return m_pasteboardName == "data interaction pasteboard";
284 }
285
286 void Pasteboard::readRespectingUTIFidelities(PasteboardWebContentReader& reader)
287 {
288     ASSERT(respectsUTIFidelities());
289     auto& strategy = *platformStrategies()->pasteboardStrategy();
290     for (NSUInteger index = 0, numberOfItems = strategy.getPasteboardItemsCount(m_pasteboardName); index < numberOfItems; ++index) {
291         // Try to read data from each type identifier that this pasteboard item supports, and WebKit also recognizes. Type identifiers are
292         // read in order of fidelity, as specified by each pasteboard item.
293         Vector<String> typesForItemInOrderOfFidelity;
294         strategy.getTypesByFidelityForItemAtIndex(typesForItemInOrderOfFidelity, index, m_pasteboardName);
295         for (auto& type : typesForItemInOrderOfFidelity) {
296             if (readPasteboardWebContentDataForType(reader, strategy, type, index, m_pasteboardName))
297                 break;
298         }
299     }
300 }
301
302 long Pasteboard::changeCount() const
303 {
304     return platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName);
305 }
306
307 NSArray *Pasteboard::supportedWebContentPasteboardTypes()
308 {
309     return @[(id)WebArchivePboardType, (id)kUTTypeFlatRTFD, (id)kUTTypeRTF, (id)kUTTypeHTML, (id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF, (id)kUTTypeURL, (id)kUTTypeText];
310 }
311
312 NSArray *Pasteboard::supportedFileUploadPasteboardTypes()
313 {
314     return @[ (NSString *)kUTTypeContent, (NSString *)kUTTypeZipArchive, (NSString *)kUTTypeFolder ];
315 }
316
317 bool Pasteboard::hasData()
318 {
319     return !!platformStrategies()->pasteboardStrategy()->getPasteboardItemsCount(m_pasteboardName);
320 }
321
322 static String utiTypeFromCocoaType(NSString *type)
323 {
324     RetainPtr<CFStringRef> utiType = adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)type, NULL));
325     if (!utiType)
326         return String();
327     return String(adoptCF(UTTypeCopyPreferredTagWithClass(utiType.get(), kUTTagClassMIMEType)).get());
328 }
329
330 static RetainPtr<NSString> cocoaTypeFromHTMLClipboardType(const String& type)
331 {
332     // Ignore any trailing charset - JS strings are Unicode, which encapsulates the charset issue.
333     if (type == "text/plain")
334         return (NSString *)kUTTypePlainText;
335
336     // Special case because UTI doesn't work with Cocoa's URL type.
337     if (type == "text/uri-list")
338         return (NSString *)kUTTypeURL;
339
340     // Try UTI now.
341     if (NSString *utiType = utiTypeFromCocoaType(type))
342         return utiType;
343
344     // No mapping, just pass the whole string though.
345     return (NSString *)type;
346 }
347
348 void Pasteboard::clear(const String& type)
349 {
350     // Since UIPasteboard enforces changeCount itself on writing, we don't check it here.
351
352     RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
353     if (!cocoaType)
354         return;
355
356     platformStrategies()->pasteboardStrategy()->writeToPasteboard(cocoaType.get(), String(), m_pasteboardName);
357 }
358
359 void Pasteboard::clear()
360 {
361     platformStrategies()->pasteboardStrategy()->writeToPasteboard(String(), String(), m_pasteboardName);
362 }
363
364 String Pasteboard::readString(const String& type)
365 {
366     PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
367
368     int numberOfItems = strategy.getPasteboardItemsCount(m_pasteboardName);
369
370     if (!numberOfItems)
371         return String();
372
373     // Grab the value off the pasteboard corresponding to the cocoaType.
374     RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
375
376     NSString *cocoaValue = nil;
377
378     if ([cocoaType isEqualToString:(NSString *)kUTTypeURL]) {
379         String title;
380         URL url = strategy.readURLFromPasteboard(0, kUTTypeURL, m_pasteboardName, title);
381         if (!url.isNull())
382             cocoaValue = [(NSURL *)url absoluteString];
383     } else if ([cocoaType isEqualToString:(NSString *)kUTTypeText]) {
384         String value = strategy.readStringFromPasteboard(0, kUTTypeText, m_pasteboardName);
385         if (!value.isNull())
386             cocoaValue = [(NSString *)value precomposedStringWithCanonicalMapping];
387     } else if (cocoaType) {
388         if (RefPtr<SharedBuffer> buffer = strategy.readBufferFromPasteboard(0, cocoaType.get(), m_pasteboardName))
389             cocoaValue = [[[NSString alloc] initWithData:buffer->createNSData().get() encoding:NSUTF8StringEncoding] autorelease];
390     }
391
392     // Enforce changeCount ourselves for security. We check after reading instead of before to be
393     // sure it doesn't change between our testing the change count and accessing the data.
394     if (cocoaValue && m_changeCount == changeCountForPasteboard(m_pasteboardName))
395         return cocoaValue;
396
397     return String();
398 }
399
400 static void addHTMLClipboardTypesForCocoaType(ListHashSet<String>& resultTypes, NSString *cocoaType)
401 {
402     // UTI may not do these right, so make sure we get the right, predictable result.
403     if ([cocoaType isEqualToString:(NSString *)kUTTypeText]) {
404         resultTypes.add(ASCIILiteral("text/plain"));
405         return;
406     }
407     if ([cocoaType isEqualToString:(NSString *)kUTTypeURL]) {
408         resultTypes.add(ASCIILiteral("text/uri-list"));
409         return;
410     }
411     String utiType = utiTypeFromCocoaType(cocoaType);
412     if (!utiType.isEmpty()) {
413         resultTypes.add(utiType);
414         return;
415     }
416     // No mapping, just pass the whole string though.
417     resultTypes.add(cocoaType);
418 }
419
420 void Pasteboard::writeString(const String& type, const String& data)
421 {
422     RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
423     if (!cocoaType)
424         return;
425
426     platformStrategies()->pasteboardStrategy()->writeToPasteboard(cocoaType.get(), data, m_pasteboardName);
427 }
428
429 Vector<String> Pasteboard::types()
430 {
431     NSArray *types = supportedWebContentPasteboardTypes();
432
433     // Enforce changeCount ourselves for security. We check after reading instead of before to be
434     // sure it doesn't change between our testing the change count and accessing the data.
435     if (m_changeCount != changeCountForPasteboard(m_pasteboardName))
436         return Vector<String>();
437
438     ListHashSet<String> result;
439     NSUInteger count = [types count];
440     for (NSUInteger i = 0; i < count; i++) {
441         NSString *type = [types objectAtIndex:i];
442         addHTMLClipboardTypesForCocoaType(result, type);
443     }
444
445     Vector<String> vector;
446     copyToVector(result, vector);
447     return vector;
448 }
449
450 Vector<String> Pasteboard::readFilenames()
451 {
452     Vector<String> filenames;
453     // Currently, data interaction is the only case on iOS where the pasteboard may contain relevant filenames.
454     platformStrategies()->pasteboardStrategy()->getFilenamesForDataInteraction(filenames, m_pasteboardName);
455     return filenames;
456 }
457
458 }