2 * Copyright (C) 2004, 2005, 2006, 2008 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, 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 COMPUTER, 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.
27 #import "ClipboardMac.h"
29 #import "DOMElementInternal.h"
30 #import "DragClient.h"
31 #import "DragController.h"
33 #import "FoundationExtras.h"
38 #import "Pasteboard.h"
39 #import "RenderImage.h"
40 #import "SecurityOrigin.h"
41 #import "WebCoreSystemInterface.h"
43 #ifdef BUILDING_ON_TIGER
44 typedef unsigned NSUInteger;
49 ClipboardMac::ClipboardMac(bool forDragging, NSPasteboard *pasteboard, ClipboardAccessPolicy policy, Frame *frame)
50 : Clipboard(policy, forDragging)
51 , m_pasteboard(pasteboard)
54 m_changeCount = [m_pasteboard.get() changeCount];
57 ClipboardMac::~ClipboardMac()
61 bool ClipboardMac::hasData()
63 return m_pasteboard && [m_pasteboard.get() types] && [[m_pasteboard.get() types] count] > 0;
66 static NSString *cocoaTypeFromHTMLClipboardType(const String& type)
68 String qType = type.stripWhiteSpace();
70 // two special cases for IE compatibility
72 return NSStringPboardType;
74 return NSURLPboardType;
76 // Ignore any trailing charset - JS strings are Unicode, which encapsulates the charset issue
77 if (qType == "text/plain" || qType.startsWith("text/plain;"))
78 return NSStringPboardType;
79 if (qType == "text/uri-list")
80 // special case because UTI doesn't work with Cocoa's URL type
81 return NSURLPboardType; // note special case in getData to read NSFilenamesType
84 NSString *mimeType = qType;
85 RetainPtr<CFStringRef> utiType(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)mimeType, NULL));
87 CFStringRef pbType = UTTypeCopyPreferredTagWithClass(utiType.get(), kUTTagClassNSPboardType);
89 return HardAutorelease(pbType);
92 // No mapping, just pass the whole string though
96 static String utiTypeFromCocoaType(NSString *type)
98 RetainPtr<CFStringRef> utiType(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassNSPboardType, (CFStringRef)type, NULL));
100 RetainPtr<CFStringRef> mimeType(AdoptCF, UTTypeCopyPreferredTagWithClass(utiType.get(), kUTTagClassMIMEType));
102 return String(mimeType.get());
107 static void addHTMLClipboardTypesForCocoaType(HashSet<String>& resultTypes, NSString *cocoaType)
109 // UTI may not do these right, so make sure we get the right, predictable result
110 if ([cocoaType isEqualToString:NSStringPboardType])
111 resultTypes.add("text/plain");
112 else if ([cocoaType isEqualToString:NSURLPboardType])
113 resultTypes.add("text/uri-list");
114 else if ([cocoaType isEqualToString:NSFilenamesPboardType]) {
115 // It is unknown if NSFilenamesPboardType always implies NSURLPboardType in Cocoa,
116 // but NSFilenamesPboardType should imply both 'text/uri-list' and 'Files'
117 resultTypes.add("text/uri-list");
118 resultTypes.add("Files");
119 } else if (String utiType = utiTypeFromCocoaType(cocoaType))
120 resultTypes.add(utiType);
122 // No mapping, just pass the whole string though
123 resultTypes.add(cocoaType);
127 void ClipboardMac::clearData(const String& type)
129 if (policy() != ClipboardWritable)
132 // note NSPasteboard enforces changeCount itself on writing - can't write if not the owner
134 NSString *cocoaType = cocoaTypeFromHTMLClipboardType(type);
136 [m_pasteboard.get() setString:@"" forType:cocoaType];
139 void ClipboardMac::clearAllData()
141 if (policy() != ClipboardWritable)
144 // note NSPasteboard enforces changeCount itself on writing - can't write if not the owner
146 [m_pasteboard.get() declareTypes:[NSArray array] owner:nil];
149 static NSArray *absoluteURLsFromPasteboardFilenames(NSPasteboard* pasteboard, bool onlyFirstURL = false)
151 NSArray *fileList = [pasteboard propertyListForType:NSFilenamesPboardType];
153 // FIXME: Why does this code need to guard against bad values on the pasteboard?
154 ASSERT(!fileList || [fileList isKindOfClass:[NSArray class]]);
155 if (!fileList || ![fileList isKindOfClass:[NSArray class]] || ![fileList count])
158 NSUInteger count = onlyFirstURL ? 1 : [fileList count];
159 NSMutableArray *urls = [NSMutableArray array];
160 for (NSUInteger i = 0; i < count; i++) {
161 NSString *string = [fileList objectAtIndex:i];
163 ASSERT([string isKindOfClass:[NSString class]]); // Added to understand why this if code is here
164 if (![string isKindOfClass:[NSString class]])
165 return nil; // Non-string object in the list, bail out! FIXME: When can this happen?
167 NSURL *url = [NSURL fileURLWithPath:string];
168 [urls addObject:[url absoluteString]];
173 static NSArray *absoluteURLsFromPasteboard(NSPasteboard* pasteboard, bool onlyFirstURL = false)
175 // NOTE: We must always check [availableTypes containsObject:] before accessing pasteboard data
176 // or CoreFoundation will printf when there is not data of the corresponding type.
177 NSArray *availableTypes = [pasteboard types];
179 // Try NSFilenamesPboardType because it contains a list
180 if ([availableTypes containsObject:NSFilenamesPboardType]) {
181 if (NSArray* absoluteURLs = absoluteURLsFromPasteboardFilenames(pasteboard, onlyFirstURL))
185 // Fallback to NSURLPboardType (which is a single URL)
186 if ([availableTypes containsObject:NSURLPboardType]) {
187 if (NSURL *url = [NSURL URLFromPasteboard:pasteboard])
188 return [NSArray arrayWithObject:[url absoluteString]];
191 // No file paths on the pasteboard, return nil
195 String ClipboardMac::getData(const String& type, bool& success) const
198 if (policy() != ClipboardReadable)
201 NSString *cocoaType = cocoaTypeFromHTMLClipboardType(type);
202 NSString *cocoaValue = nil;
204 // Grab the value off the pasteboard corresponding to the cocoaType
205 if ([cocoaType isEqualToString:NSURLPboardType]) {
206 // "URL" and "text/url-list" both map to NSURLPboardType in cocoaTypeFromHTMLClipboardType(), "URL" only wants the first URL
207 bool onlyFirstURL = (type == "URL");
208 NSArray *absoluteURLs = absoluteURLsFromPasteboard(m_pasteboard.get(), onlyFirstURL);
209 cocoaValue = [absoluteURLs componentsJoinedByString:@"\n"];
210 } else if ([cocoaType isEqualToString:NSStringPboardType]) {
211 cocoaValue = [[m_pasteboard.get() stringForType:cocoaType] precomposedStringWithCanonicalMapping];
212 } else if (cocoaType)
213 cocoaValue = [m_pasteboard.get() stringForType:cocoaType];
215 // Enforce changeCount ourselves for security. We check after reading instead of before to be
216 // sure it doesn't change between our testing the change count and accessing the data.
217 if (cocoaValue && m_changeCount == [m_pasteboard.get() changeCount]) {
225 bool ClipboardMac::setData(const String &type, const String &data)
227 if (policy() != ClipboardWritable)
229 // note NSPasteboard enforces changeCount itself on writing - can't write if not the owner
231 NSString *cocoaType = cocoaTypeFromHTMLClipboardType(type);
232 NSString *cocoaData = data;
234 if ([cocoaType isEqualToString:NSURLPboardType]) {
235 [m_pasteboard.get() addTypes:[NSArray arrayWithObject:NSURLPboardType] owner:nil];
236 NSURL *url = [[NSURL alloc] initWithString:cocoaData];
237 [url writeToPasteboard:m_pasteboard.get()];
239 if ([url isFileURL] && m_frame->document()->securityOrigin()->canLoadLocalResources()) {
240 [m_pasteboard.get() addTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil];
241 NSArray *fileList = [NSArray arrayWithObject:[url path]];
242 [m_pasteboard.get() setPropertyList:fileList forType:NSFilenamesPboardType];
250 // everything else we know of goes on the pboard as a string
251 [m_pasteboard.get() addTypes:[NSArray arrayWithObject:cocoaType] owner:nil];
252 return [m_pasteboard.get() setString:cocoaData forType:cocoaType];
258 HashSet<String> ClipboardMac::types() const
260 if (policy() != ClipboardReadable && policy() != ClipboardTypesReadable)
261 return HashSet<String>();
263 NSArray *types = [m_pasteboard.get() types];
265 // Enforce changeCount ourselves for security. We check after reading instead of before to be
266 // sure it doesn't change between our testing the change count and accessing the data.
267 if (m_changeCount != [m_pasteboard.get() changeCount])
268 return HashSet<String>();
270 HashSet<String> result;
271 NSUInteger count = [types count];
272 // FIXME: This loop could be split into two stages. One which adds all the HTML5 specified types
273 // and a second which adds all the extra types from the cocoa clipboard (which is Mac-only behavior).
274 for (NSUInteger i = 0; i < count; i++) {
275 NSString *pbType = [types objectAtIndex:i];
276 if ([pbType isEqualToString:@"NeXT plain ascii pasteboard type"])
277 continue; // skip this ancient type that gets auto-supplied by some system conversion
279 addHTMLClipboardTypesForCocoaType(result, pbType);
285 // FIXME: We could cache the computed fileList if necessary
286 // Currently each access gets a new copy, setData() modifications to the
287 // clipboard are not reflected in any FileList objects the page has accessed and stored
288 PassRefPtr<FileList> ClipboardMac::files() const
290 if (policy() != ClipboardReadable)
291 return FileList::create();
293 NSArray *absoluteURLs = absoluteURLsFromPasteboardFilenames(m_pasteboard.get());
294 NSUInteger count = [absoluteURLs count];
296 RefPtr<FileList> fileList = FileList::create();
297 for (NSUInteger x = 0; x < count; x++) {
298 NSURL *absoluteURL = [NSURL URLWithString:[absoluteURLs objectAtIndex:x]];
299 ASSERT([absoluteURL isFileURL]);
300 fileList->append(File::create([absoluteURL path]));
302 return fileList.release(); // We will always return a FileList, sometimes empty
305 // The rest of these getters don't really have any impact on security, so for now make no checks
307 void ClipboardMac::setDragImage(CachedImage* img, const IntPoint &loc)
309 setDragImage(img, 0, loc);
312 void ClipboardMac::setDragImageElement(Node *node, const IntPoint &loc)
314 setDragImage(0, node, loc);
317 void ClipboardMac::setDragImage(CachedImage* image, Node *node, const IntPoint &loc)
319 if (policy() == ClipboardImageWritable || policy() == ClipboardWritable) {
321 m_dragImage->removeClient(this);
324 m_dragImage->addClient(this);
327 m_dragImageElement = node;
329 if (dragStarted() && m_changeCount == [m_pasteboard.get() changeCount]) {
331 NSImage* cocoaImage = dragNSImage(cocoaLoc);
333 // Dashboard wants to be able to set the drag image during dragging, but Cocoa does not allow this.
334 // Instead we must drop down to the CoreGraphics API.
335 wkSetDragImage(cocoaImage, cocoaLoc);
337 // Hack: We must post an event to wake up the NSDragManager, which is sitting in a nextEvent call
338 // up the stack from us because the CoreFoundation drag manager does not use the run loop by itself.
339 // This is the most innocuous event to use, per Kristen Forster.
340 NSEvent* ev = [NSEvent mouseEventWithType:NSMouseMoved location:NSZeroPoint
341 modifierFlags:0 timestamp:0 windowNumber:0 context:nil eventNumber:0 clickCount:0 pressure:0];
342 [NSApp postEvent:ev atStart:YES];
345 // Else either 1) we haven't started dragging yet, so we rely on the part to install this drag image
346 // as part of getting the drag kicked off, or 2) Someone kept a ref to the clipboard and is trying to
347 // set the image way too late.
351 void ClipboardMac::writeRange(Range* range, Frame* frame)
355 Pasteboard::writeSelection(m_pasteboard.get(), range, frame->editor()->smartInsertDeleteEnabled() && frame->selectionGranularity() == WordGranularity, frame);
358 void ClipboardMac::writeURL(const KURL& url, const String& title, Frame* frame)
361 ASSERT(m_pasteboard);
362 Pasteboard::writeURL(m_pasteboard.get(), nil, url, title, frame);
365 void ClipboardMac::declareAndWriteDragImage(Element* element, const KURL& url, const String& title, Frame* frame)
368 if (Page* page = frame->page())
369 page->dragController()->client()->declareAndWriteDragImage(m_pasteboard.get(), kit(element), url, title, frame);
372 DragImageRef ClipboardMac::createDragImage(IntPoint& loc) const
374 NSPoint nsloc = {loc.x(), loc.y()};
375 DragImageRef result = dragNSImage(nsloc);
376 loc = (IntPoint)nsloc;
380 NSImage *ClipboardMac::dragNSImage(NSPoint& loc) const
382 NSImage *result = nil;
383 if (m_dragImageElement) {
387 result = m_frame->snapshotDragImage(m_dragImageElement.get(), &imageRect, &elementRect);
388 // Client specifies point relative to element, not the whole image, which may include child
389 // layers spread out all over the place.
390 loc.x = elementRect.origin.x - imageRect.origin.x + m_dragLoc.x();
391 loc.y = elementRect.origin.y - imageRect.origin.y + m_dragLoc.y();
392 loc.y = imageRect.size.height - loc.y;
394 } else if (m_dragImage) {
395 result = m_dragImage->image()->getNSImage();
398 loc.y = [result size].height - loc.y;