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