Home | History | Annotate | Download | only in mac
      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
     45 typedef unsigned NSUInteger;
     46 #endif
     47 
     48 namespace WebCore {
     49 
     50 PassRefPtr<Clipboard> Clipboard::create(ClipboardAccessPolicy policy, DragData* dragData, Frame* frame)
     51 {
     52     return ClipboardMac::create(DragAndDrop, dragData->pasteboard(), policy, frame);
     53 }
     54 
     55 ClipboardMac::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 
     63 ClipboardMac::~ClipboardMac()
     64 {
     65 }
     66 
     67 bool ClipboardMac::hasData()
     68 {
     69     return m_pasteboard && [m_pasteboard.get() types] && [[m_pasteboard.get() types] count] > 0;
     70 }
     71 
     72 static 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 
    102 static 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 
    113 static 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 
    146 void 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 
    157 void 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 
    167 static 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 
    191 static 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 
    213 String 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 
    243 bool 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 
    276 HashSet<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
    306 PassRefPtr<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 
    325 void ClipboardMac::setDragImage(CachedImage* img, const IntPoint &loc)
    326 {
    327     setDragImage(img, 0, loc);
    328 }
    329 
    330 void ClipboardMac::setDragImageElement(Node *node, const IntPoint &loc)
    331 {
    332     setDragImage(0, node, loc);
    333 }
    334 
    335 void 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 
    369 void 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 
    376 void ClipboardMac::writePlainText(const String& text)
    377 {
    378     Pasteboard::writePlainText(m_pasteboard.get(), text);
    379 }
    380 
    381 void 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)
    389 void 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 
    397 DragImageRef 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 
    405 NSImage *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