[Attachment Support] Create attachment elements when dropping files on iOS
[WebKit-https.git] / Source / WebCore / platform / ios / PlatformPasteboardIOS.mm
1 /*
2  * Copyright (C) 2013-2017 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 "PlatformPasteboard.h"
28
29 #import "Color.h"
30 #import "Image.h"
31 #import "Pasteboard.h"
32 #import "SharedBuffer.h"
33 #import "URL.h"
34 #import "UTIUtilities.h"
35 #import "WebItemProviderPasteboard.h"
36 #import <MobileCoreServices/MobileCoreServices.h>
37 #import <UIKit/UIImage.h>
38 #import <UIKit/UIPasteboard.h>
39 #import <pal/spi/cocoa/NSKeyedArchiverSPI.h>
40 #import <pal/spi/ios/UIKitSPI.h>
41 #import <wtf/ListHashSet.h>
42 #import <wtf/SoftLinking.h>
43 #import <wtf/text/StringHash.h>
44
45 #define PASTEBOARD_SUPPORTS_ITEM_PROVIDERS (PLATFORM(IOS) && !(PLATFORM(WATCHOS) || PLATFORM(APPLETV)))
46
47 SOFT_LINK_FRAMEWORK(UIKit)
48 SOFT_LINK_CLASS(UIKit, UIImage)
49 SOFT_LINK_CLASS(UIKit, UIPasteboard)
50
51 namespace WebCore {
52
53 PlatformPasteboard::PlatformPasteboard()
54     : m_pasteboard([getUIPasteboardClass() generalPasteboard])
55 {
56 }
57
58 #if PASTEBOARD_SUPPORTS_ITEM_PROVIDERS
59 PlatformPasteboard::PlatformPasteboard(const String& name)
60 {
61     if (name == "data interaction pasteboard")
62         m_pasteboard = [WebItemProviderPasteboard sharedInstance];
63     else
64         m_pasteboard = [getUIPasteboardClass() generalPasteboard];
65 }
66 #else
67 PlatformPasteboard::PlatformPasteboard(const String&)
68     : m_pasteboard([getUIPasteboardClass() generalPasteboard])
69 {
70 }
71 #endif
72
73 void PlatformPasteboard::getTypes(Vector<String>& types)
74 {
75     for (NSString *pasteboardType in [m_pasteboard pasteboardTypes])
76         types.append(pasteboardType);
77 }
78
79 void PlatformPasteboard::getTypesByFidelityForItemAtIndex(Vector<String>& types, int index)
80 {
81     if (index >= [m_pasteboard numberOfItems] || ![m_pasteboard respondsToSelector:@selector(pasteboardTypesByFidelityForItemAtIndex:)])
82         return;
83
84     NSArray *pasteboardTypesByFidelity = [m_pasteboard pasteboardTypesByFidelityForItemAtIndex:index];
85     for (NSString *typeIdentifier in pasteboardTypesByFidelity)
86         types.append(typeIdentifier);
87 }
88
89 RefPtr<SharedBuffer> PlatformPasteboard::bufferForType(const String& type)
90 {
91     if (NSData *data = [m_pasteboard dataForPasteboardType:type])
92         return SharedBuffer::create(data);
93     return nullptr;
94 }
95
96 void PlatformPasteboard::getPathnamesForType(Vector<String>&, const String&) const
97 {
98 }
99
100 int PlatformPasteboard::numberOfFiles() const
101 {
102     return [m_pasteboard respondsToSelector:@selector(numberOfFiles)] ? [m_pasteboard numberOfFiles] : 0;
103 }
104
105 #if PASTEBOARD_SUPPORTS_ITEM_PROVIDERS
106
107 static PasteboardItemPresentationStyle pasteboardItemPresentationStyle(UIPreferredPresentationStyle style)
108 {
109     switch (style) {
110     case UIPreferredPresentationStyleUnspecified:
111         return PasteboardItemPresentationStyle::Unspecified;
112     case UIPreferredPresentationStyleInline:
113         return PasteboardItemPresentationStyle::Inline;
114     case UIPreferredPresentationStyleAttachment:
115         return PasteboardItemPresentationStyle::Attachment;
116     default:
117         ASSERT_NOT_REACHED();
118         return PasteboardItemPresentationStyle::Unspecified;
119     }
120 }
121
122 PasteboardItemInfo PlatformPasteboard::informationForItemAtIndex(int index)
123 {
124     if (index >= [m_pasteboard numberOfItems])
125         return { };
126
127     PasteboardItemInfo info;
128     if ([m_pasteboard respondsToSelector:@selector(preferredFileUploadURLAtIndex:fileType:)]) {
129         NSString *fileType = nil;
130         info.pathForFileUpload = [m_pasteboard preferredFileUploadURLAtIndex:index fileType:&fileType].path;
131         info.contentTypeForFileUpload = fileType;
132     }
133
134     NSItemProvider *itemProvider = [[m_pasteboard itemProviders] objectAtIndex:index];
135     info.preferredPresentationStyle = pasteboardItemPresentationStyle(itemProvider.preferredPresentationStyle);
136     return info;
137 }
138
139 #else
140
141 PasteboardItemInfo PlatformPasteboard::informationForItemAtIndex(int)
142 {
143     return { };
144 }
145
146 #endif
147
148 static bool pasteboardMayContainFilePaths(id<AbstractPasteboard> pasteboard)
149 {
150 #if PASTEBOARD_SUPPORTS_ITEM_PROVIDERS
151     if ([pasteboard isKindOfClass:[WebItemProviderPasteboard class]])
152         return false;
153 #endif
154
155     for (NSString *type in pasteboard.pasteboardTypes) {
156         if (Pasteboard::shouldTreatCocoaTypeAsFile(type))
157             return true;
158     }
159     return false;
160 }
161
162 String PlatformPasteboard::stringForType(const String& type) const
163 {
164     auto value = retainPtr([m_pasteboard valuesForPasteboardType:type inItemSet:[NSIndexSet indexSetWithIndex:0]].firstObject);
165     String result;
166     if ([value isKindOfClass:[NSURL class]])
167         result = [(NSURL *)value absoluteString];
168
169     else if ([value isKindOfClass:[NSAttributedString class]])
170         result = [(NSAttributedString *)value string];
171
172     else if ([value isKindOfClass:[NSString class]])
173         result = (NSString *)value;
174
175     if (pasteboardMayContainFilePaths(m_pasteboard.get()) && type == String { kUTTypeURL }) {
176         if (!Pasteboard::canExposeURLToDOMWhenPasteboardContainsFiles(result))
177             result = { };
178     }
179
180     return result;
181 }
182
183 Color PlatformPasteboard::color()
184 {
185     return Color();
186 }
187
188 URL PlatformPasteboard::url()
189 {
190     return URL();
191 }
192
193 long PlatformPasteboard::copy(const String&)
194 {
195     return 0;
196 }
197
198 long PlatformPasteboard::addTypes(const Vector<String>&)
199 {
200     return 0;
201 }
202
203 long PlatformPasteboard::setTypes(const Vector<String>&)
204 {
205     return 0;
206 }
207
208 long PlatformPasteboard::setBufferForType(SharedBuffer*, const String&)
209 {
210     return 0;
211 }
212
213 long PlatformPasteboard::setPathnamesForType(const Vector<String>&, const String&)
214 {
215     return 0;
216 }
217
218 long PlatformPasteboard::setStringForType(const String&, const String&)
219 {
220     return 0;
221 }
222
223 long PlatformPasteboard::changeCount() const
224 {
225     return [m_pasteboard changeCount];
226 }
227
228 String PlatformPasteboard::uniqueName()
229 {
230     return String();
231 }
232
233 String PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(const String& domType)
234 {
235     if (domType == "text/plain")
236         return kUTTypePlainText;
237
238     if (domType == "text/html")
239         return kUTTypeHTML;
240
241     if (domType == "text/uri-list")
242         return kUTTypeURL;
243
244     return { };
245 }
246
247 #if PASTEBOARD_SUPPORTS_ITEM_PROVIDERS
248
249 static NSString *webIOSPastePboardType = @"iOS rich content paste pasteboard type";
250
251 static void registerItemToPasteboard(WebItemProviderRegistrationInfoList *representationsToRegister, id <AbstractPasteboard> pasteboard)
252 {
253     if (UIItemProvider *itemProvider = representationsToRegister.itemProvider)
254         [pasteboard setItemProviders:@[ itemProvider ]];
255     else
256         [pasteboard setItemProviders:@[ ]];
257
258     if ([pasteboard respondsToSelector:@selector(stageRegistrationList:)])
259         [pasteboard stageRegistrationList:representationsToRegister];
260 }
261
262 static void addRepresentationsForPlainText(WebItemProviderRegistrationInfoList *itemsToRegister, const String& plainText)
263 {
264     if (plainText.isEmpty())
265         return;
266
267     NSURL *platformURL = [NSURL URLWithString:plainText];
268     if (URL(platformURL).isValid())
269         [itemsToRegister addRepresentingObject:platformURL];
270
271     [itemsToRegister addData:[(NSString *)plainText dataUsingEncoding:NSUTF8StringEncoding] forType:(NSString *)kUTTypeUTF8PlainText];
272 }
273
274 bool PlatformPasteboard::allowReadingURLAtIndex(const URL& url, int index) const
275 {
276     NSItemProvider *itemProvider = (NSUInteger)index < [m_pasteboard itemProviders].count ? [[m_pasteboard itemProviders] objectAtIndex:index] : nil;
277     for (NSString *type in itemProvider.registeredTypeIdentifiers) {
278         if (UTTypeConformsTo((CFStringRef)type, kUTTypeURL))
279             return true;
280     }
281
282     return url.isValid();
283 }
284
285 void PlatformPasteboard::write(const PasteboardWebContent& content)
286 {
287     auto representationsToRegister = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]);
288
289     [representationsToRegister addData:[webIOSPastePboardType dataUsingEncoding:NSUTF8StringEncoding] forType:webIOSPastePboardType];
290
291     ASSERT(content.clientTypes.size() == content.clientData.size());
292     for (size_t i = 0, size = content.clientTypes.size(); i < size; ++i)
293         [representationsToRegister addData:content.clientData[i]->createNSData().get() forType:content.clientTypes[i]];
294
295     if (content.dataInWebArchiveFormat)
296         [representationsToRegister addData:content.dataInWebArchiveFormat->createNSData().get() forType:WebArchivePboardType];
297
298     if (content.dataInAttributedStringFormat) {
299         NSAttributedString *attributedString = unarchivedObjectOfClassesFromData([NSSet setWithObject:[NSAttributedString class]], content.dataInAttributedStringFormat->createNSData().get());
300         if (attributedString)
301             [representationsToRegister addRepresentingObject:attributedString];
302     }
303
304     if (content.dataInRTFDFormat)
305         [representationsToRegister addData:content.dataInRTFDFormat->createNSData().get() forType:(NSString *)kUTTypeFlatRTFD];
306
307     if (content.dataInRTFFormat)
308         [representationsToRegister addData:content.dataInRTFFormat->createNSData().get() forType:(NSString *)kUTTypeRTF];
309
310     if (!content.dataInHTMLFormat.isEmpty()) {
311         NSData *htmlAsData = [(NSString *)content.dataInHTMLFormat dataUsingEncoding:NSUTF8StringEncoding];
312         [representationsToRegister addData:htmlAsData forType:(NSString *)kUTTypeHTML];
313     }
314
315     if (!content.dataInStringFormat.isEmpty())
316         addRepresentationsForPlainText(representationsToRegister.get(), content.dataInStringFormat);
317
318     PasteboardCustomData customData;
319     customData.origin = content.contentOrigin;
320     [representationsToRegister addData:customData.createSharedBuffer()->createNSData().get() forType:@(PasteboardCustomData::cocoaType())];
321
322     registerItemToPasteboard(representationsToRegister.get(), m_pasteboard.get());
323 }
324
325 void PlatformPasteboard::write(const PasteboardImage& pasteboardImage)
326 {
327     auto representationsToRegister = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]);
328
329     auto& types = pasteboardImage.clientTypes;
330     auto& data = pasteboardImage.clientData;
331     ASSERT(types.size() == data.size());
332     for (size_t i = 0, size = types.size(); i < size; ++i)
333         [representationsToRegister addData:data[i]->createNSData().get() forType:types[i]];
334
335     if (pasteboardImage.resourceData && !pasteboardImage.resourceMIMEType.isEmpty()) {
336         auto utiOrMIMEType = pasteboardImage.resourceMIMEType;
337         if (!isDeclaredUTI(utiOrMIMEType))
338             utiOrMIMEType = UTIFromMIMEType(utiOrMIMEType);
339
340         auto imageData = pasteboardImage.resourceData->createNSData();
341         [representationsToRegister addData:imageData.get() forType:(NSString *)utiOrMIMEType];
342         [representationsToRegister setPreferredPresentationSize:pasteboardImage.imageSize];
343         [representationsToRegister setSuggestedName:pasteboardImage.suggestedName];
344     }
345
346     // FIXME: When writing a PasteboardImage, we currently always place the image data at a higer fidelity than the
347     // associated image URL. However, in the case of an image enclosed by an anchor, we might want to consider the
348     // the URL (i.e. the anchor's href attribute) to be a higher fidelity representation.
349     if (!pasteboardImage.url.url.isEmpty()) {
350         if (NSURL *nsURL = pasteboardImage.url.url)
351             [representationsToRegister addRepresentingObject:nsURL];
352     }
353
354     registerItemToPasteboard(representationsToRegister.get(), m_pasteboard.get());
355 }
356
357 void PlatformPasteboard::write(const String& pasteboardType, const String& text)
358 {
359     auto representationsToRegister = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]);
360     [representationsToRegister setPreferredPresentationStyle:WebPreferredPresentationStyleInline];
361
362     NSString *pasteboardTypeAsNSString = pasteboardType;
363     if (!text.isEmpty() && pasteboardTypeAsNSString.length) {
364         auto pasteboardTypeAsCFString = (CFStringRef)pasteboardTypeAsNSString;
365         if (UTTypeConformsTo(pasteboardTypeAsCFString, kUTTypeURL) || UTTypeConformsTo(pasteboardTypeAsCFString, kUTTypeText))
366             addRepresentationsForPlainText(representationsToRegister.get(), text);
367         else
368             [representationsToRegister addData:[pasteboardTypeAsNSString dataUsingEncoding:NSUTF8StringEncoding] forType:pasteboardType];
369     }
370
371     registerItemToPasteboard(representationsToRegister.get(), m_pasteboard.get());
372 }
373
374 void PlatformPasteboard::write(const PasteboardURL& url)
375 {
376     auto representationsToRegister = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]);
377     [representationsToRegister setPreferredPresentationStyle:WebPreferredPresentationStyleInline];
378
379     if (NSURL *nsURL = url.url) {
380         if (!url.title.isEmpty())
381             nsURL._title = url.title;
382         [representationsToRegister addRepresentingObject:nsURL];
383     }
384
385     registerItemToPasteboard(representationsToRegister.get(), m_pasteboard.get());
386 }
387
388 static const char *safeTypeForDOMToReadAndWriteForPlatformType(const String& platformType)
389 {
390     auto cfType = platformType.createCFString();
391     if (UTTypeConformsTo(cfType.get(), kUTTypePlainText))
392         return ASCIILiteral("text/plain");
393
394     if (UTTypeConformsTo(cfType.get(), kUTTypeHTML) || UTTypeConformsTo(cfType.get(), (CFStringRef)WebArchivePboardType)
395         || UTTypeConformsTo(cfType.get(), kUTTypeRTF) || UTTypeConformsTo(cfType.get(), kUTTypeFlatRTFD))
396         return ASCIILiteral("text/html");
397
398     if (UTTypeConformsTo(cfType.get(), kUTTypeURL))
399         return ASCIILiteral("text/uri-list");
400
401     return nullptr;
402 }
403
404 static const char originKeyForTeamData[] = "com.apple.WebKit.drag-and-drop-team-data.origin";
405 static const char customTypesKeyForTeamData[] = "com.apple.WebKit.drag-and-drop-team-data.custom-types";
406
407 Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite(const String& origin) const
408 {
409     ListHashSet<String> domPasteboardTypes;
410     for (NSItemProvider *provider in [m_pasteboard itemProviders]) {
411         if (!provider.teamData.length)
412             continue;
413
414         NSDictionary *teamDataObject = unarchivedObjectOfClassesFromData([NSSet setWithObjects:[NSDictionary class], [NSString class], [NSArray class], nil], provider.teamData);
415         if (!teamDataObject)
416             continue;
417
418         id originInTeamData = [teamDataObject objectForKey:@(originKeyForTeamData)];
419         if (![originInTeamData isKindOfClass:[NSString class]])
420             continue;
421         if (String((NSString *)originInTeamData) != origin)
422             continue;
423
424         id customTypes = [(NSDictionary *)teamDataObject objectForKey:@(customTypesKeyForTeamData)];
425         if (![customTypes isKindOfClass:[NSArray class]])
426             continue;
427
428         for (NSString *type in customTypes)
429             domPasteboardTypes.add(type);
430     }
431
432     if (NSData *serializedCustomData = [m_pasteboard dataForPasteboardType:@(PasteboardCustomData::cocoaType())]) {
433         auto data = PasteboardCustomData::fromSharedBuffer(SharedBuffer::create(serializedCustomData).get());
434         if (data.origin == origin) {
435             for (auto& type : data.orderedTypes)
436                 domPasteboardTypes.add(type);
437         }
438     }
439
440     for (NSString *type in [m_pasteboard pasteboardTypes]) {
441         if ([type isEqualToString:@(PasteboardCustomData::cocoaType())])
442             continue;
443
444         if (Pasteboard::isSafeTypeForDOMToReadAndWrite(type)) {
445             domPasteboardTypes.add(type);
446             continue;
447         }
448
449         if (auto* coercedType = safeTypeForDOMToReadAndWriteForPlatformType(type)) {
450             auto domTypeAsString = String::fromUTF8(coercedType);
451             if (domTypeAsString == "text/uri-list") {
452                 BOOL ableToDetermineProtocolOfPasteboardURL = ![m_pasteboard isKindOfClass:[WebItemProviderPasteboard class]];
453                 if (ableToDetermineProtocolOfPasteboardURL && stringForType(kUTTypeURL).isEmpty())
454                     continue;
455             }
456             domPasteboardTypes.add(WTFMove(domTypeAsString));
457         }
458     }
459
460     return copyToVector(domPasteboardTypes);
461 }
462
463 long PlatformPasteboard::write(const PasteboardCustomData& data)
464 {
465     auto representationsToRegister = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]);
466     [representationsToRegister setPreferredPresentationStyle:WebPreferredPresentationStyleInline];
467
468     if (data.sameOriginCustomData.size()) {
469         if (auto serializedSharedBuffer = data.createSharedBuffer()->createNSData()) {
470             // We stash the list of supplied pasteboard types in teamData here for compatibility with drag and drop.
471             // Since the contents of item providers cannot be loaded prior to drop, but the pasteboard types are
472             // contained within the custom data blob and we need to vend them to the page when firing `dragover`
473             // events, we need an additional in-memory representation of the pasteboard types array that contains
474             // all of the custom types. We use the teamData property, available on NSItemProvider on iOS, to store
475             // this information, since the contents of teamData are immediately available prior to the drop.
476             NSMutableArray<NSString *> *typesAsNSArray = [NSMutableArray array];
477             for (auto& type : data.orderedTypes)
478                 [typesAsNSArray addObject:type];
479             [representationsToRegister setTeamData:securelyArchivedDataWithRootObject(@{ @(originKeyForTeamData) : data.origin, @(customTypesKeyForTeamData) : typesAsNSArray })];
480             [representationsToRegister addData:serializedSharedBuffer.get() forType:@(PasteboardCustomData::cocoaType())];
481         }
482     }
483
484     for (auto& type : data.orderedTypes) {
485         NSString *stringValue = data.platformData.get(type);
486         if (!stringValue.length)
487             continue;
488
489         auto cocoaType = platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(type).createCFString();
490         if (UTTypeConformsTo(cocoaType.get(), kUTTypeURL))
491             [representationsToRegister addRepresentingObject:[NSURL URLWithString:stringValue]];
492         else if (UTTypeConformsTo(cocoaType.get(), kUTTypePlainText))
493             [representationsToRegister addRepresentingObject:stringValue];
494         else
495             [representationsToRegister addData:[stringValue dataUsingEncoding:NSUTF8StringEncoding] forType:(NSString *)cocoaType.get()];
496     }
497
498     registerItemToPasteboard(representationsToRegister.get(), m_pasteboard.get());
499     return [m_pasteboard changeCount];
500 }
501
502 #else
503
504 bool PlatformPasteboard::allowReadingURLAtIndex(const URL&, int) const
505 {
506     return false;
507 }
508
509 void PlatformPasteboard::write(const PasteboardWebContent&)
510 {
511 }
512
513 void PlatformPasteboard::write(const PasteboardImage&)
514 {
515 }
516
517 void PlatformPasteboard::write(const String&, const String&)
518 {
519 }
520
521 void PlatformPasteboard::write(const PasteboardURL&)
522 {
523 }
524
525 Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite(const String&) const
526 {
527     return { };
528 }
529
530 long PlatformPasteboard::write(const PasteboardCustomData&)
531 {
532     return 0;
533 }
534
535 #endif
536
537 int PlatformPasteboard::count()
538 {
539     return [m_pasteboard numberOfItems];
540 }
541
542 RefPtr<SharedBuffer> PlatformPasteboard::readBuffer(int index, const String& type)
543 {
544     NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:index];
545
546     RetainPtr<NSArray> pasteboardItem = [m_pasteboard dataForPasteboardType:type inItemSet:indexSet];
547
548     if (![pasteboardItem count])
549         return nullptr;
550     return SharedBuffer::create([pasteboardItem.get() objectAtIndex:0]);
551 }
552
553 String PlatformPasteboard::readString(int index, const String& type)
554 {
555     NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:index];
556
557     NSArray *pasteboardValues = [m_pasteboard valuesForPasteboardType:type inItemSet:indexSet];
558     if (!pasteboardValues.count) {
559         NSArray<NSData *> *pasteboardData = [m_pasteboard dataForPasteboardType:type inItemSet:indexSet];
560         if (!pasteboardData.count)
561             return { };
562         pasteboardValues = pasteboardData;
563     }
564
565     RetainPtr<id> value = [pasteboardValues objectAtIndex:0];
566     if ([value isKindOfClass:[NSData class]])
567         value = adoptNS([[NSString alloc] initWithData:(NSData *)value.get() encoding:NSUTF8StringEncoding]);
568     
569     if (type == String(kUTTypePlainText) || type == String(kUTTypeHTML)) {
570         ASSERT([value isKindOfClass:[NSString class]]);
571         return [value isKindOfClass:[NSString class]] ? value.get() : nil;
572     }
573     if (type == String(kUTTypeText)) {
574         ASSERT([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSAttributedString class]]);
575         if ([value isKindOfClass:[NSString class]])
576             return value.get();
577         if ([value isKindOfClass:[NSAttributedString class]])
578             return [(NSAttributedString *)value string];
579     } else if (type == String(kUTTypeURL)) {
580         ASSERT([value isKindOfClass:[NSURL class]] || [value isKindOfClass:[NSString class]]);
581         if ([value isKindOfClass:[NSString class]])
582             value = [NSURL URLWithString:value.get()];
583         if ([value isKindOfClass:[NSURL class]] && allowReadingURLAtIndex((NSURL *)value, index))
584             return [(NSURL *)value absoluteString];
585     }
586
587     return String();
588 }
589
590 URL PlatformPasteboard::readURL(int index, const String& type, String& title)
591 {
592     NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:index];
593     RetainPtr<NSArray> pasteboardItem = [m_pasteboard valuesForPasteboardType:type inItemSet:indexSet];
594
595     if (![pasteboardItem count])
596         return { };
597
598     id value = [pasteboardItem objectAtIndex:0];
599     NSURL *url = nil;
600     if ([value isKindOfClass:[NSData class]]) {
601         id plist = [NSPropertyListSerialization propertyListWithData:(NSData *)value options:NSPropertyListImmutable format:NULL error:NULL];
602         if (![plist isKindOfClass:[NSArray class]])
603             return { };
604         NSArray *plistArray = (NSArray *)plist;
605         if (plistArray.count < 2)
606             return { };
607         if (plistArray.count == 2)
608             url = [NSURL URLWithString:plistArray[0]];
609         else // The first string is the relative URL.
610             url = [NSURL URLWithString:plistArray[0] relativeToURL:[NSURL URLWithString:plistArray[1]]];
611     } else {
612         ASSERT([value isKindOfClass:[NSURL class]]);
613         if (![value isKindOfClass:[NSURL class]])
614             return { };
615         url = (NSURL *)value;
616     }
617
618     if (!allowReadingURLAtIndex(url, index))
619         return { };
620
621 #if PASTEBOARD_SUPPORTS_ITEM_PROVIDERS
622     title = [url _title];
623 #else
624     UNUSED_PARAM(title);
625 #endif
626
627     return url;
628 }
629
630 void PlatformPasteboard::updateSupportedTypeIdentifiers(const Vector<String>& types)
631 {
632     if (![m_pasteboard respondsToSelector:@selector(updateSupportedTypeIdentifiers:)])
633         return;
634
635     NSMutableArray *typesArray = [NSMutableArray arrayWithCapacity:types.size()];
636     for (auto type : types)
637         [typesArray addObject:(NSString *)type];
638
639     [m_pasteboard updateSupportedTypeIdentifiers:typesArray];
640 }
641
642 }