On ToT, event.dataTransfer.getData("text/uri-list") returns an empty string when...
[WebKit-https.git] / Source / WebCore / platform / mac / PlatformPasteboardMac.mm
1 /*
2  * Copyright (C) 2006 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 "Pasteboard.h"
31 #import "URL.h"
32 #import "SharedBuffer.h"
33 #import <wtf/HashCountedSet.h>
34 #import <wtf/ListHashSet.h>
35 #import <wtf/text/StringHash.h>
36
37 namespace WebCore {
38
39 PlatformPasteboard::PlatformPasteboard(const String& pasteboardName)
40     : m_pasteboard([NSPasteboard pasteboardWithName:pasteboardName])
41 {
42     ASSERT(pasteboardName);
43 }
44
45 void PlatformPasteboard::getTypes(Vector<String>& types)
46 {
47     NSArray *pasteboardTypes = [m_pasteboard.get() types];
48
49     for (NSUInteger i = 0; i < [pasteboardTypes count]; i++)
50         types.append([pasteboardTypes objectAtIndex:i]);
51 }
52
53 RefPtr<SharedBuffer> PlatformPasteboard::bufferForType(const String& pasteboardType)
54 {
55     NSData *data = [m_pasteboard.get() dataForType:pasteboardType];
56     if (!data)
57         return nullptr;
58     return SharedBuffer::create([[data copy] autorelease]);
59 }
60
61 int PlatformPasteboard::numberOfFiles() const
62 {
63     Vector<String> files;
64     getPathnamesForType(files, String(NSFilenamesPboardType));
65     if (!files.size())
66         getPathnamesForType(files, String(NSFilesPromisePboardType));
67     return files.size();
68 }
69
70 void PlatformPasteboard::getPathnamesForType(Vector<String>& pathnames, const String& pasteboardType) const
71 {
72     NSArray* paths = [m_pasteboard.get() propertyListForType:pasteboardType];
73     if ([paths isKindOfClass:[NSString class]]) {
74         pathnames.append((NSString *)paths);
75         return;
76     }
77     for (NSUInteger i = 0; i < [paths count]; i++)
78         pathnames.append([paths objectAtIndex:i]);
79 }
80
81 static bool pasteboardMayContainFilePaths(NSPasteboard *pasteboard)
82 {
83     for (NSString *type in pasteboard.types) {
84         if ([type isEqualToString:(NSString *)NSFilenamesPboardType] || [type isEqualToString:(NSString *)NSFilesPromisePboardType] || Pasteboard::shouldTreatCocoaTypeAsFile(type))
85             return true;
86     }
87     return false;
88 }
89
90 String PlatformPasteboard::stringForType(const String& pasteboardType) const
91 {
92     if (pasteboardType == String { NSURLPboardType }) {
93         String urlString = ([NSURL URLFromPasteboard:m_pasteboard.get()] ?: [NSURL URLWithString:[m_pasteboard stringForType:NSURLPboardType]]).absoluteString;
94         if (pasteboardMayContainFilePaths(m_pasteboard.get()) && !Pasteboard::canExposeURLToDOMWhenPasteboardContainsFiles(urlString))
95             return { };
96         return urlString;
97     }
98
99     return [m_pasteboard stringForType:pasteboardType];
100 }
101
102 static const char* safeTypeForDOMToReadAndWriteForPlatformType(const String& platformType)
103 {
104     if (platformType == String(NSStringPboardType) || platformType == String(NSPasteboardTypeString))
105         return ASCIILiteral("text/plain");
106
107     if (platformType == String(NSURLPboardType))
108         return ASCIILiteral("text/uri-list");
109
110     if (platformType == String(NSHTMLPboardType))
111         return ASCIILiteral("text/html");
112
113     return nullptr;
114 }
115
116 Vector<String> PlatformPasteboard::typesSafeForDOMToReadAndWrite(const String& origin) const
117 {
118     ListHashSet<String> domPasteboardTypes;
119     if (NSData *serializedCustomData = [m_pasteboard dataForType:@(PasteboardCustomData::cocoaType())]) {
120         auto data = PasteboardCustomData::fromSharedBuffer(SharedBuffer::create(serializedCustomData).get());
121         if (data.origin == origin) {
122             for (auto& type : data.orderedTypes)
123                 domPasteboardTypes.add(type);
124         }
125     }
126
127     NSArray<NSString *> *allTypes = [m_pasteboard types];
128     for (NSString *type in allTypes) {
129         if ([type isEqualToString:@(PasteboardCustomData::cocoaType())])
130             continue;
131
132         if (Pasteboard::isSafeTypeForDOMToReadAndWrite(type))
133             domPasteboardTypes.add(type);
134         else if (auto* domType = safeTypeForDOMToReadAndWriteForPlatformType(type)) {
135             auto domTypeAsString = String::fromUTF8(domType);
136             if (domTypeAsString == "text/uri-list" && stringForType(NSURLPboardType).isEmpty())
137                 continue;
138             domPasteboardTypes.add(WTFMove(domTypeAsString));
139         }
140     }
141
142     return copyToVector(domPasteboardTypes);
143 }
144
145 long PlatformPasteboard::write(const PasteboardCustomData& data)
146 {
147     NSMutableArray *types = [NSMutableArray array];
148     for (auto& entry : data.platformData)
149         [types addObject:platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(entry.key)];
150     if (data.sameOriginCustomData.size())
151         [types addObject:@(PasteboardCustomData::cocoaType())];
152
153     [m_pasteboard declareTypes:types owner:nil];
154
155     for (auto& entry : data.platformData) {
156         auto platformType = platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(entry.key);
157         ASSERT(!platformType.isEmpty());
158         if (!platformType.isEmpty())
159             [m_pasteboard setString:entry.value forType:platformType];
160     }
161
162     if (data.sameOriginCustomData.size()) {
163         if (auto serializedCustomData = data.createSharedBuffer()->createNSData())
164             [m_pasteboard setData:serializedCustomData.get() forType:@(PasteboardCustomData::cocoaType())];
165     }
166
167     return changeCount();
168 }
169
170 long PlatformPasteboard::changeCount() const
171 {
172     return [m_pasteboard.get() changeCount];
173 }
174
175 String PlatformPasteboard::platformPasteboardTypeForSafeTypeForDOMToReadAndWrite(const String& domType)
176 {
177     if (domType == "text/plain")
178         return NSStringPboardType;
179
180     if (domType == "text/html")
181         return NSHTMLPboardType;
182
183     if (domType == "text/uri-list")
184         return NSURLPboardType;
185
186     return { };
187 }
188
189 String PlatformPasteboard::uniqueName()
190 {
191     return [[NSPasteboard pasteboardWithUniqueName] name];
192 }
193
194 Color PlatformPasteboard::color()
195 {
196     NSColor *color = [NSColor colorFromPasteboard:m_pasteboard.get()];
197
198     // The color may not be in an RGB colorspace. This commonly occurs when a color is
199     // dragged from the NSColorPanel grayscale picker.
200     if ([[color colorSpace] colorSpaceModel] != NSRGBColorSpaceModel)
201         color = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
202
203     return makeRGBA((int)([color redComponent] * 255.0 + 0.5), (int)([color greenComponent] * 255.0 + 0.5),
204         (int)([color blueComponent] * 255.0 + 0.5), (int)([color alphaComponent] * 255.0 + 0.5));
205 }
206
207 URL PlatformPasteboard::url()
208 {
209     return [NSURL URLFromPasteboard:m_pasteboard.get()];
210 }
211
212 long PlatformPasteboard::copy(const String& fromPasteboard)
213 {
214     NSPasteboard* pasteboard = [NSPasteboard pasteboardWithName:fromPasteboard];
215     NSArray* types = [pasteboard types];
216
217     [m_pasteboard.get() addTypes:types owner:nil];
218     for (NSUInteger i = 0; i < [types count]; i++) {
219         NSString* type = [types objectAtIndex:i];
220         if (![m_pasteboard.get() setData:[pasteboard dataForType:type] forType:type])
221             return 0;
222     }
223     return changeCount();
224 }
225
226 long PlatformPasteboard::addTypes(const Vector<String>& pasteboardTypes)
227 {
228     RetainPtr<NSMutableArray> types = adoptNS([[NSMutableArray alloc] init]);
229     for (size_t i = 0; i < pasteboardTypes.size(); ++i)
230         [types.get() addObject:pasteboardTypes[i]];
231
232     return [m_pasteboard.get() addTypes:types.get() owner:nil];
233 }
234
235 long PlatformPasteboard::setTypes(const Vector<String>& pasteboardTypes)
236 {
237     if (pasteboardTypes.isEmpty())
238         return [m_pasteboard declareTypes:@[] owner:nil];
239
240     RetainPtr<NSMutableArray> types = adoptNS([[NSMutableArray alloc] init]);
241     for (size_t i = 0; i < pasteboardTypes.size(); ++i)
242         [types.get() addObject:pasteboardTypes[i]];
243
244     return [m_pasteboard.get() declareTypes:types.get() owner:nil];
245 }
246
247 long PlatformPasteboard::setBufferForType(SharedBuffer* buffer, const String& pasteboardType)
248 {
249     BOOL didWriteData = [m_pasteboard setData:buffer ? buffer->createNSData().get() : nil forType:pasteboardType];
250     if (!didWriteData)
251         return 0;
252     return changeCount();
253 }
254
255 long PlatformPasteboard::setPathnamesForType(const Vector<String>& pathnames, const String& pasteboardType)
256 {
257     RetainPtr<NSMutableArray> paths = adoptNS([[NSMutableArray alloc] init]);
258     for (size_t i = 0; i < pathnames.size(); ++i)
259         [paths.get() addObject:[NSArray arrayWithObject:pathnames[i]]];
260     BOOL didWriteData = [m_pasteboard.get() setPropertyList:paths.get() forType:pasteboardType];
261     if (!didWriteData)
262         return 0;
263     return changeCount();
264 }
265
266 long PlatformPasteboard::setStringForType(const String& string, const String& pasteboardType)
267 {
268     BOOL didWriteData;
269
270     if (pasteboardType == String(NSURLPboardType)) {
271         // We cannot just use -NSPasteboard writeObjects:], because -declareTypes has been already called, implicitly creating an item.
272         NSURL *url = [NSURL URLWithString:string];
273         if ([[m_pasteboard.get() types] containsObject:NSURLPboardType]) {
274             NSURL *base = [url baseURL];
275             if (base)
276                 didWriteData = [m_pasteboard.get() setPropertyList:@[[url relativeString], [base absoluteString]] forType:NSURLPboardType];
277             else if (url)
278                 didWriteData = [m_pasteboard.get() setPropertyList:@[[url absoluteString], @""] forType:NSURLPboardType];
279             else
280                 didWriteData = [m_pasteboard.get() setPropertyList:@[@"", @""] forType:NSURLPboardType];
281
282             if (!didWriteData)
283                 return 0;
284         }
285
286         if ([[m_pasteboard.get() types] containsObject:(NSString *)kUTTypeURL]) {
287             didWriteData = [m_pasteboard.get() setString:[url absoluteString] forType:(NSString *)kUTTypeURL];
288             if (!didWriteData)
289                 return 0;
290         }
291
292         if ([[m_pasteboard.get() types] containsObject:(NSString *)kUTTypeFileURL] && [url isFileURL]) {
293             didWriteData = [m_pasteboard.get() setString:[url absoluteString] forType:(NSString *)kUTTypeFileURL];
294             if (!didWriteData)
295                 return 0;
296         }
297
298     } else {
299         didWriteData = [m_pasteboard.get() setString:string forType:pasteboardType];
300         if (!didWriteData)
301             return 0;
302     }
303
304     return changeCount();
305 }
306
307 }