1/*
2 * Copyright (C) 2011 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 "ClipboardIOS.h"
27
28#import "DragData.h"
29#import "Editor.h"
30#import "EditorClient.h"
31#import "FileList.h"
32#import "Frame.h"
33#import "Pasteboard.h"
34
35#include "SoftLinking.h"
36#include <MobileCoreServices/MobileCoreServices.h>
37
38SOFT_LINK_FRAMEWORK(MobileCoreServices)
39
40SOFT_LINK(MobileCoreServices, UTTypeCreatePreferredIdentifierForTag, CFStringRef, (CFStringRef inTagClass, CFStringRef inTag, CFStringRef inConformingToUTI), (inTagClass, inTag, inConformingToUTI))
41SOFT_LINK(MobileCoreServices, UTTypeCopyPreferredTagWithClass, CFStringRef, (CFStringRef inUTI, CFStringRef inTagClass), (inUTI, inTagClass))
42
43SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeText, CFStringRef)
44SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeURL, CFStringRef)
45SOFT_LINK_CONSTANT(MobileCoreServices, kUTTagClassMIMEType, CFStringRef)
46
47#define kUTTypeText getkUTTypeText()
48#define kUTTypeURL  getkUTTypeURL()
49#define kUTTypeTIFF getkUTTypeTIFF()
50#define kUTTagClassMIMEType getkUTTagClassMIMEType()
51
52namespace WebCore {
53
54PassRefPtr<Clipboard> Clipboard::create(ClipboardAccessPolicy policy, DragData*, Frame* frame)
55{
56    return ClipboardIOS::create(DragAndDrop, policy, frame);
57}
58
59ClipboardIOS::ClipboardIOS(ClipboardType clipboardType, ClipboardAccessPolicy policy, Frame* frame)
60    : Clipboard(policy, clipboardType)
61    , m_frame(frame)
62{
63    m_changeCount = m_frame->editor().client()->pasteboardChangeCount();
64}
65
66ClipboardIOS::~ClipboardIOS()
67{
68}
69
70bool ClipboardIOS::hasData()
71{
72    return m_frame->editor().client()->getPasteboardItemsCount() != 0;
73}
74
75static String utiTypeFromCocoaType(NSString* type)
76{
77    RetainPtr<CFStringRef> utiType = adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)type, NULL));
78    if (utiType) {
79        RetainPtr<CFStringRef> mimeType = adoptCF(UTTypeCopyPreferredTagWithClass(utiType.get(), kUTTagClassMIMEType));
80        if (mimeType)
81            return String(mimeType.get());
82    }
83    return String();
84}
85
86static RetainPtr<NSString> cocoaTypeFromHTMLClipboardType(const String& type)
87{
88    String qType = type.stripWhiteSpace();
89
90    if (qType == "Text")
91        return (NSString*)kUTTypeText;
92    if (qType == "URL")
93        return (NSString*)kUTTypeURL;
94
95    // Ignore any trailing charset - JS strings are Unicode, which encapsulates the charset issue.
96    if (qType.startsWith(ASCIILiteral("text/plain")))
97        return (NSString*)kUTTypeText;
98    if (qType == "text/uri-list")
99        // Special case because UTI doesn't work with Cocoa's URL type.
100        return (NSString*)kUTTypeURL;
101
102    // Try UTI now.
103    NSString *pbType = utiTypeFromCocoaType(qType);
104    if (pbType)
105        return pbType;
106
107    // No mapping, just pass the whole string though.
108    return (NSString*)qType;
109}
110
111static void addHTMLClipboardTypesForCocoaType(ListHashSet<String>& resultTypes, NSString* cocoaType)
112{
113    // UTI may not do these right, so make sure we get the right, predictable result.
114    if ([cocoaType isEqualToString:(NSString*)kUTTypeText]) {
115        resultTypes.add(ASCIILiteral("text/plain"));
116        return;
117    }
118    if ([cocoaType isEqualToString:(NSString*)kUTTypeURL]) {
119        resultTypes.add(ASCIILiteral("text/uri-list"));
120        return;
121    }
122    String utiType = utiTypeFromCocoaType(cocoaType);
123    if (!utiType.isEmpty()) {
124        resultTypes.add(utiType);
125        return;
126    }
127    // No mapping, just pass the whole string though.
128    resultTypes.add(cocoaType);
129}
130
131void ClipboardIOS::clearData(const String& type)
132{
133    if (!canWriteData())
134        return;
135
136    // Note UIPasteboard enforces changeCount itself on writing - can't write if not the owner.
137
138    if (RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type)) {
139        RetainPtr<NSDictionary> representations = adoptNS([[NSMutableDictionary alloc] init]);
140        [representations.get() setValue:0 forKey:cocoaType.get()];
141        m_frame->editor().client()->writeDataToPasteboard(representations.get());
142    }
143}
144
145void ClipboardIOS::clearData()
146{
147    if (!canWriteData())
148        return;
149
150    RetainPtr<NSDictionary> representations = adoptNS([[NSMutableDictionary alloc] init]);
151    m_frame->editor().client()->writeDataToPasteboard(representations.get());
152}
153
154String ClipboardIOS::getData(const String& type) const
155{
156    if (!canReadData())
157        return String();
158
159    RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
160    NSString *cocoaValue = nil;
161
162    // Grab the value off the pasteboard corresponding to the cocoaType.
163    RetainPtr<NSArray> pasteboardItem = m_frame->editor().client()->readDataFromPasteboard(cocoaType.get(), 0);
164
165    if ([pasteboardItem.get() count] == 0)
166        return String();
167
168    if ([cocoaType.get() isEqualToString:(NSString*)kUTTypeURL]) {
169        id value = [pasteboardItem.get() objectAtIndex:0];
170        if (![value isKindOfClass:[NSURL class]]) {
171            ASSERT([value isKindOfClass:[NSURL class]]);
172            return String();
173        }
174        NSURL* absoluteURL = (NSURL*)value;
175
176        if (absoluteURL)
177            cocoaValue = [absoluteURL absoluteString];
178    } else if ([cocoaType.get() isEqualToString:(NSString*)kUTTypeText]) {
179        id value = [pasteboardItem.get() objectAtIndex:0];
180        if (![value isKindOfClass:[NSString class]]) {
181            ASSERT([value isKindOfClass:[NSString class]]);
182            return String();
183        }
184
185        cocoaValue = [(NSString*)value precomposedStringWithCanonicalMapping];
186    } else if (cocoaType) {
187        ASSERT([pasteboardItem.get() count] == 1);
188        id value = [pasteboardItem.get() objectAtIndex:0];
189        if (![value isKindOfClass:[NSData class]]) {
190            ASSERT([value isKindOfClass:[NSData class]]);
191            return String();
192        }
193        cocoaValue = [[[NSString alloc] initWithData:(NSData *)value encoding:NSUTF8StringEncoding] autorelease];
194    }
195
196    // Enforce changeCount ourselves for security.  We check after reading instead of before to be
197    // sure it doesn't change between our testing the change count and accessing the data.
198    if (cocoaValue && m_changeCount == m_frame->editor().client()->pasteboardChangeCount())
199        return cocoaValue;
200
201    return String();
202}
203
204bool ClipboardIOS::setData(const String& type, const String& data)
205{
206    if (!canWriteData())
207        return false;
208
209    RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
210    NSString *cocoaData = data;
211
212    RetainPtr<NSDictionary> representations = adoptNS([[NSMutableDictionary alloc] init]);
213
214    if ([cocoaType.get() isEqualToString:(NSString*)kUTTypeURL]) {
215        RetainPtr<NSURL> url = adoptNS([[NSURL alloc] initWithString:cocoaData]);
216        [representations.get() setValue:url.get() forKey:(NSString*)kUTTypeURL];
217        m_frame->editor().client()->writeDataToPasteboard(representations.get());
218        return true;
219    }
220
221    if (cocoaType) {
222        // Everything else we know of goes on the pboard as the original string
223        // we received as parameter.
224        [representations.get() setValue:cocoaData forKey:cocoaType.get()];
225        m_frame->editor().client()->writeDataToPasteboard(representations.get());
226        return true;
227    }
228
229    return false;
230}
231
232ListHashSet<String> ClipboardIOS::types() const
233{
234    if (!canReadTypes())
235        return ListHashSet<String>();
236
237    NSArray* types = Pasteboard::supportedPasteboardTypes();
238
239    // Enforce changeCount ourselves for security.  We check after reading instead of before to be
240    // sure it doesn't change between our testing the change count and accessing the data.
241    if (m_changeCount != m_frame->editor().client()->pasteboardChangeCount())
242        return ListHashSet<String>();
243
244    ListHashSet<String> result;
245    NSUInteger count = [types count];
246    for (NSUInteger i = 0; i < count; i++) {
247        NSString* pbType = [types objectAtIndex:i];
248        addHTMLClipboardTypesForCocoaType(result, pbType);
249    }
250
251    return result;
252}
253
254PassRefPtr<FileList> ClipboardIOS::files() const
255{
256    return 0;
257}
258
259void ClipboardIOS::writeRange(Range* range, Frame* frame)
260{
261    ASSERT(range);
262    ASSERT(frame);
263    Pasteboard::generalPasteboard()->writeSelection(range, frame->editor().smartInsertDeleteEnabled() && frame->selection()->granularity() == WordGranularity, frame);
264}
265
266void ClipboardIOS::writePlainText(const String& text)
267{
268    Pasteboard::generalPasteboard()->writePlainText(text, m_frame);
269}
270
271void ClipboardIOS::writeURL(const KURL&, const String&, Frame*)
272{
273}
274
275#if ENABLE(DRAG_SUPPORT)
276void ClipboardIOS::declareAndWriteDragImage(Element* element, const KURL& url, const String& title, Frame* frame)
277{
278    ASSERT(frame);
279    if (Page* page = frame->page())
280        page->dragController()->client()->declareAndWriteDragImage(m_pasteboard.get(), kit(element), url, title, frame);
281}
282#endif // ENABLE(DRAG_SUPPORT)
283
284DragImageRef ClipboardIOS::createDragImage(IntPoint&) const
285{
286    return 0;
287}
288
289void ClipboardIOS::setDragImage(CachedImage*, const IntPoint&)
290{
291}
292
293void ClipboardIOS::setDragImageElement(Node *, const IntPoint&)
294{
295}
296
297}
298