• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2004, 2005, 2006, 2008, 2010 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 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.
24 */
25
26#import "config.h"
27#import "ClipboardMac.h"
28
29#import "DOMElementInternal.h"
30#import "DragClient.h"
31#import "DragController.h"
32#import "DragData.h"
33#import "Editor.h"
34#import "FileList.h"
35#import "Frame.h"
36#import "Image.h"
37#import "Page.h"
38#import "Pasteboard.h"
39#import "RenderImage.h"
40#import "ScriptExecutionContext.h"
41#import "SecurityOrigin.h"
42#import "WebCoreSystemInterface.h"
43
44#ifdef BUILDING_ON_TIGER
45typedef unsigned NSUInteger;
46#endif
47
48namespace WebCore {
49
50PassRefPtr<Clipboard> Clipboard::create(ClipboardAccessPolicy policy, DragData* dragData, Frame* frame)
51{
52    return ClipboardMac::create(DragAndDrop, dragData->pasteboard(), policy, frame);
53}
54
55ClipboardMac::ClipboardMac(ClipboardType clipboardType, NSPasteboard *pasteboard, ClipboardAccessPolicy policy, Frame *frame)
56    : Clipboard(policy, clipboardType)
57    , m_pasteboard(pasteboard)
58    , m_frame(frame)
59{
60    m_changeCount = [m_pasteboard.get() changeCount];
61}
62
63ClipboardMac::~ClipboardMac()
64{
65}
66
67bool ClipboardMac::hasData()
68{
69    return m_pasteboard && [m_pasteboard.get() types] && [[m_pasteboard.get() types] count] > 0;
70}
71
72static RetainPtr<NSString> cocoaTypeFromHTMLClipboardType(const String& type)
73{
74    String qType = type.stripWhiteSpace();
75
76    // two special cases for IE compatibility
77    if (qType == "Text")
78        return NSStringPboardType;
79    if (qType == "URL")
80        return NSURLPboardType;
81
82    // Ignore any trailing charset - JS strings are Unicode, which encapsulates the charset issue
83    if (qType == "text/plain" || qType.startsWith("text/plain;"))
84        return NSStringPboardType;
85    if (qType == "text/uri-list")
86        // special case because UTI doesn't work with Cocoa's URL type
87        return NSURLPboardType; // note special case in getData to read NSFilenamesType
88
89    // Try UTI now
90    NSString *mimeType = qType;
91    RetainPtr<CFStringRef> utiType(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)mimeType, NULL));
92    if (utiType) {
93        CFStringRef pbType = UTTypeCopyPreferredTagWithClass(utiType.get(), kUTTagClassNSPboardType);
94        if (pbType)
95            return (NSString *)pbType;
96    }
97
98    // No mapping, just pass the whole string though
99    return (NSString *)qType;
100}
101
102static String utiTypeFromCocoaType(NSString *type)
103{
104    RetainPtr<CFStringRef> utiType(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassNSPboardType, (CFStringRef)type, NULL));
105    if (utiType) {
106        RetainPtr<CFStringRef> mimeType(AdoptCF, UTTypeCopyPreferredTagWithClass(utiType.get(), kUTTagClassMIMEType));
107        if (mimeType)
108            return String(mimeType.get());
109    }
110    return String();
111}
112
113static void addHTMLClipboardTypesForCocoaType(HashSet<String>& resultTypes, NSString *cocoaType, NSPasteboard *pasteboard)
114{
115    // UTI may not do these right, so make sure we get the right, predictable result
116    if ([cocoaType isEqualToString:NSStringPboardType]) {
117        resultTypes.add("text/plain");
118        return;
119    }
120    if ([cocoaType isEqualToString:NSURLPboardType]) {
121        resultTypes.add("text/uri-list");
122        return;
123    }
124    if ([cocoaType isEqualToString:NSFilenamesPboardType]) {
125        // If file list is empty, add nothing.
126        // Note that there is a chance that the file list count could have changed since we grabbed the types array.
127        // However, this is not really an issue for us doing a sanity check here.
128        NSArray *fileList = [pasteboard propertyListForType:NSFilenamesPboardType];
129        if ([fileList count]) {
130            // It is unknown if NSFilenamesPboardType always implies NSURLPboardType in Cocoa,
131            // but NSFilenamesPboardType should imply both 'text/uri-list' and 'Files'
132            resultTypes.add("text/uri-list");
133            resultTypes.add("Files");
134        }
135        return;
136    }
137    String utiType = utiTypeFromCocoaType(cocoaType);
138    if (!utiType.isEmpty()) {
139        resultTypes.add(utiType);
140        return;
141    }
142    // No mapping, just pass the whole string though
143    resultTypes.add(cocoaType);
144}
145
146void ClipboardMac::clearData(const String& type)
147{
148    if (policy() != ClipboardWritable)
149        return;
150
151    // note NSPasteboard enforces changeCount itself on writing - can't write if not the owner
152
153    if (RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type))
154        [m_pasteboard.get() setString:@"" forType:cocoaType.get()];
155}
156
157void ClipboardMac::clearAllData()
158{
159    if (policy() != ClipboardWritable)
160        return;
161
162    // note NSPasteboard enforces changeCount itself on writing - can't write if not the owner
163
164    [m_pasteboard.get() declareTypes:[NSArray array] owner:nil];
165}
166
167static NSArray *absoluteURLsFromPasteboardFilenames(NSPasteboard* pasteboard, bool onlyFirstURL = false)
168{
169    NSArray *fileList = [pasteboard propertyListForType:NSFilenamesPboardType];
170
171    // FIXME: Why does this code need to guard against bad values on the pasteboard?
172    ASSERT(!fileList || [fileList isKindOfClass:[NSArray class]]);
173    if (!fileList || ![fileList isKindOfClass:[NSArray class]] || ![fileList count])
174        return nil;
175
176    NSUInteger count = onlyFirstURL ? 1 : [fileList count];
177    NSMutableArray *urls = [NSMutableArray array];
178    for (NSUInteger i = 0; i < count; i++) {
179        NSString *string = [fileList objectAtIndex:i];
180
181        ASSERT([string isKindOfClass:[NSString class]]);  // Added to understand why this if code is here
182        if (![string isKindOfClass:[NSString class]])
183            return nil; // Non-string object in the list, bail out!  FIXME: When can this happen?
184
185        NSURL *url = [NSURL fileURLWithPath:string];
186        [urls addObject:[url absoluteString]];
187    }
188    return urls;
189}
190
191static NSArray *absoluteURLsFromPasteboard(NSPasteboard* pasteboard, bool onlyFirstURL = false)
192{
193    // NOTE: We must always check [availableTypes containsObject:] before accessing pasteboard data
194    // or CoreFoundation will printf when there is not data of the corresponding type.
195    NSArray *availableTypes = [pasteboard types];
196
197    // Try NSFilenamesPboardType because it contains a list
198    if ([availableTypes containsObject:NSFilenamesPboardType]) {
199        if (NSArray* absoluteURLs = absoluteURLsFromPasteboardFilenames(pasteboard, onlyFirstURL))
200            return absoluteURLs;
201    }
202
203    // Fallback to NSURLPboardType (which is a single URL)
204    if ([availableTypes containsObject:NSURLPboardType]) {
205        if (NSURL *url = [NSURL URLFromPasteboard:pasteboard])
206            return [NSArray arrayWithObject:[url absoluteString]];
207    }
208
209    // No file paths on the pasteboard, return nil
210    return nil;
211}
212
213String ClipboardMac::getData(const String& type, bool& success) const
214{
215    success = false;
216    if (policy() != ClipboardReadable)
217        return String();
218
219    RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
220    NSString *cocoaValue = nil;
221
222    // Grab the value off the pasteboard corresponding to the cocoaType
223    if ([cocoaType.get() isEqualToString:NSURLPboardType]) {
224        // "URL" and "text/url-list" both map to NSURLPboardType in cocoaTypeFromHTMLClipboardType(), "URL" only wants the first URL
225        bool onlyFirstURL = (type == "URL");
226        NSArray *absoluteURLs = absoluteURLsFromPasteboard(m_pasteboard.get(), onlyFirstURL);
227        cocoaValue = [absoluteURLs componentsJoinedByString:@"\n"];
228    } else if ([cocoaType.get() isEqualToString:NSStringPboardType]) {
229        cocoaValue = [[m_pasteboard.get() stringForType:cocoaType.get()] precomposedStringWithCanonicalMapping];
230    } else if (cocoaType)
231        cocoaValue = [m_pasteboard.get() stringForType:cocoaType.get()];
232
233    // Enforce changeCount ourselves for security.  We check after reading instead of before to be
234    // sure it doesn't change between our testing the change count and accessing the data.
235    if (cocoaValue && m_changeCount == [m_pasteboard.get() changeCount]) {
236        success = true;
237        return cocoaValue;
238    }
239
240    return String();
241}
242
243bool ClipboardMac::setData(const String &type, const String &data)
244{
245    if (policy() != ClipboardWritable)
246        return false;
247    // note NSPasteboard enforces changeCount itself on writing - can't write if not the owner
248
249    RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(type);
250    NSString *cocoaData = data;
251
252    if ([cocoaType.get() isEqualToString:NSURLPboardType]) {
253        [m_pasteboard.get() addTypes:[NSArray arrayWithObject:NSURLPboardType] owner:nil];
254        NSURL *url = [[NSURL alloc] initWithString:cocoaData];
255        [url writeToPasteboard:m_pasteboard.get()];
256
257        if ([url isFileURL] && m_frame->document()->securityOrigin()->canLoadLocalResources()) {
258            [m_pasteboard.get() addTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil];
259            NSArray *fileList = [NSArray arrayWithObject:[url path]];
260            [m_pasteboard.get() setPropertyList:fileList forType:NSFilenamesPboardType];
261        }
262
263        [url release];
264        return true;
265    }
266
267    if (cocoaType) {
268        // everything else we know of goes on the pboard as a string
269        [m_pasteboard.get() addTypes:[NSArray arrayWithObject:cocoaType.get()] owner:nil];
270        return [m_pasteboard.get() setString:cocoaData forType:cocoaType.get()];
271    }
272
273    return false;
274}
275
276HashSet<String> ClipboardMac::types() const
277{
278    if (policy() != ClipboardReadable && policy() != ClipboardTypesReadable)
279        return HashSet<String>();
280
281    NSArray *types = [m_pasteboard.get() types];
282
283    // Enforce changeCount ourselves for security.  We check after reading instead of before to be
284    // sure it doesn't change between our testing the change count and accessing the data.
285    if (m_changeCount != [m_pasteboard.get() changeCount])
286        return HashSet<String>();
287
288    HashSet<String> result;
289    NSUInteger count = [types count];
290    // FIXME: This loop could be split into two stages. One which adds all the HTML5 specified types
291    // and a second which adds all the extra types from the cocoa clipboard (which is Mac-only behavior).
292    for (NSUInteger i = 0; i < count; i++) {
293        NSString *pbType = [types objectAtIndex:i];
294        if ([pbType isEqualToString:@"NeXT plain ascii pasteboard type"])
295            continue;   // skip this ancient type that gets auto-supplied by some system conversion
296
297        addHTMLClipboardTypesForCocoaType(result, pbType, m_pasteboard.get());
298    }
299
300    return result;
301}
302
303// FIXME: We could cache the computed fileList if necessary
304// Currently each access gets a new copy, setData() modifications to the
305// clipboard are not reflected in any FileList objects the page has accessed and stored
306PassRefPtr<FileList> ClipboardMac::files() const
307{
308    if (policy() != ClipboardReadable)
309        return FileList::create();
310
311    NSArray *absoluteURLs = absoluteURLsFromPasteboardFilenames(m_pasteboard.get());
312    NSUInteger count = [absoluteURLs count];
313
314    RefPtr<FileList> fileList = FileList::create();
315    for (NSUInteger x = 0; x < count; x++) {
316        NSURL *absoluteURL = [NSURL URLWithString:[absoluteURLs objectAtIndex:x]];
317        ASSERT([absoluteURL isFileURL]);
318        fileList->append(File::create([absoluteURL path]));
319    }
320    return fileList.release(); // We will always return a FileList, sometimes empty
321}
322
323// The rest of these getters don't really have any impact on security, so for now make no checks
324
325void ClipboardMac::setDragImage(CachedImage* img, const IntPoint &loc)
326{
327    setDragImage(img, 0, loc);
328}
329
330void ClipboardMac::setDragImageElement(Node *node, const IntPoint &loc)
331{
332    setDragImage(0, node, loc);
333}
334
335void ClipboardMac::setDragImage(CachedImage* image, Node *node, const IntPoint &loc)
336{
337    if (policy() == ClipboardImageWritable || policy() == ClipboardWritable) {
338        if (m_dragImage)
339            m_dragImage->removeClient(this);
340        m_dragImage = image;
341        if (m_dragImage)
342            m_dragImage->addClient(this);
343
344        m_dragLoc = loc;
345        m_dragImageElement = node;
346
347        if (dragStarted() && m_changeCount == [m_pasteboard.get() changeCount]) {
348            NSPoint cocoaLoc;
349            NSImage* cocoaImage = dragNSImage(cocoaLoc);
350            if (cocoaImage) {
351                // Dashboard wants to be able to set the drag image during dragging, but Cocoa does not allow this.
352                // Instead we must drop down to the CoreGraphics API.
353                wkSetDragImage(cocoaImage, cocoaLoc);
354
355                // Hack: We must post an event to wake up the NSDragManager, which is sitting in a nextEvent call
356                // up the stack from us because the CoreFoundation drag manager does not use the run loop by itself.
357                // This is the most innocuous event to use, per Kristen Forster.
358                NSEvent* ev = [NSEvent mouseEventWithType:NSMouseMoved location:NSZeroPoint
359                    modifierFlags:0 timestamp:0 windowNumber:0 context:nil eventNumber:0 clickCount:0 pressure:0];
360                [NSApp postEvent:ev atStart:YES];
361            }
362        }
363        // Else either 1) we haven't started dragging yet, so we rely on the part to install this drag image
364        // as part of getting the drag kicked off, or 2) Someone kept a ref to the clipboard and is trying to
365        // set the image way too late.
366    }
367}
368
369void ClipboardMac::writeRange(Range* range, Frame* frame)
370{
371    ASSERT(range);
372    ASSERT(frame);
373    Pasteboard::writeSelection(m_pasteboard.get(), 0, range, frame->editor()->smartInsertDeleteEnabled() && frame->selection()->granularity() == WordGranularity, frame);
374}
375
376void ClipboardMac::writePlainText(const String& text)
377{
378    Pasteboard::writePlainText(m_pasteboard.get(), text);
379}
380
381void ClipboardMac::writeURL(const KURL& url, const String& title, Frame* frame)
382{
383    ASSERT(frame);
384    ASSERT(m_pasteboard);
385    Pasteboard::writeURL(m_pasteboard.get(), nil, url, title, frame);
386}
387
388#if ENABLE(DRAG_SUPPORT)
389void ClipboardMac::declareAndWriteDragImage(Element* element, const KURL& url, const String& title, Frame* frame)
390{
391    ASSERT(frame);
392    if (Page* page = frame->page())
393        page->dragController()->client()->declareAndWriteDragImage(m_pasteboard.get(), kit(element), url, title, frame);
394}
395#endif // ENABLE(DRAG_SUPPORT)
396
397DragImageRef ClipboardMac::createDragImage(IntPoint& loc) const
398{
399    NSPoint nsloc = {loc.x(), loc.y()};
400    DragImageRef result = dragNSImage(nsloc);
401    loc = (IntPoint)nsloc;
402    return result;
403}
404
405NSImage *ClipboardMac::dragNSImage(NSPoint& loc) const
406{
407    NSImage *result = nil;
408    if (m_dragImageElement) {
409        if (m_frame) {
410            NSRect imageRect;
411            NSRect elementRect;
412            result = m_frame->snapshotDragImage(m_dragImageElement.get(), &imageRect, &elementRect);
413            // Client specifies point relative to element, not the whole image, which may include child
414            // layers spread out all over the place.
415            loc.x = elementRect.origin.x - imageRect.origin.x + m_dragLoc.x();
416            loc.y = elementRect.origin.y - imageRect.origin.y + m_dragLoc.y();
417            loc.y = imageRect.size.height - loc.y;
418        }
419    } else if (m_dragImage) {
420        result = m_dragImage->image()->getNSImage();
421
422        loc = m_dragLoc;
423        loc.y = [result size].height - loc.y;
424    }
425    return result;
426}
427
428}
429