1/* 2 * Copyright (C) 2006 Apple Computer, 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 "Pasteboard.h" 28 29#import "CachedResource.h" 30#import "CharacterNames.h" 31#import "DOMRangeInternal.h" 32#import "Document.h" 33#import "DocumentFragment.h" 34#import "Editor.h" 35#import "EditorClient.h" 36#import "Frame.h" 37#import "HitTestResult.h" 38#import "Image.h" 39#import "KURL.h" 40#import "LegacyWebArchive.h" 41#import "LoaderNSURLExtras.h" 42#import "MIMETypeRegistry.h" 43#import "RenderImage.h" 44#import "WebCoreNSStringExtras.h" 45#import "markup.h" 46 47#import <wtf/StdLibExtras.h> 48#import <wtf/RetainPtr.h> 49#import <wtf/UnusedParam.h> 50 51@interface NSAttributedString (AppKitSecretsIKnowAbout) 52- (id)_initWithDOMRange:(DOMRange *)domRange; 53@end 54 55namespace WebCore { 56 57// FIXME: It's not great to have these both here and in WebKit. 58NSString *WebArchivePboardType = @"Apple Web Archive pasteboard type"; 59NSString *WebSmartPastePboardType = @"NeXT smart paste pasteboard type"; 60NSString *WebURLNamePboardType = @"public.url-name"; 61NSString *WebURLPboardType = @"public.url"; 62NSString *WebURLsWithTitlesPboardType = @"WebURLsWithTitlesPboardType"; 63 64#ifndef BUILDING_ON_TIGER 65static NSArray* selectionPasteboardTypes(bool canSmartCopyOrDelete, bool selectionContainsAttachments) 66{ 67 if (selectionContainsAttachments) { 68 if (canSmartCopyOrDelete) 69 return [NSArray arrayWithObjects:WebSmartPastePboardType, WebArchivePboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil]; 70 else 71 return [NSArray arrayWithObjects:WebArchivePboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil]; 72 } else { // Don't write RTFD to the pasteboard when the copied attributed string has no attachments. 73 if (canSmartCopyOrDelete) 74 return [NSArray arrayWithObjects:WebSmartPastePboardType, WebArchivePboardType, NSRTFPboardType, NSStringPboardType, nil]; 75 else 76 return [NSArray arrayWithObjects:WebArchivePboardType, NSRTFPboardType, NSStringPboardType, nil]; 77 } 78} 79#endif 80 81static NSArray* writableTypesForURL() 82{ 83 DEFINE_STATIC_LOCAL(RetainPtr<NSArray>, types, ([[NSArray alloc] initWithObjects: 84 WebURLsWithTitlesPboardType, 85 NSURLPboardType, 86 WebURLPboardType, 87 WebURLNamePboardType, 88 NSStringPboardType, 89 nil])); 90 return types.get(); 91} 92 93static inline NSArray* createWritableTypesForImage() 94{ 95 NSMutableArray *types = [[NSMutableArray alloc] initWithObjects:NSTIFFPboardType, nil]; 96 [types addObjectsFromArray:writableTypesForURL()]; 97 [types addObject:NSRTFDPboardType]; 98 return types; 99} 100 101static NSArray* writableTypesForImage() 102{ 103 DEFINE_STATIC_LOCAL(RetainPtr<NSArray>, types, (createWritableTypesForImage())); 104 return types.get(); 105} 106 107Pasteboard* Pasteboard::generalPasteboard() 108{ 109 static Pasteboard* pasteboard = new Pasteboard([NSPasteboard generalPasteboard]); 110 return pasteboard; 111} 112 113Pasteboard::Pasteboard(NSPasteboard* pboard) 114 : m_pasteboard(pboard) 115{ 116} 117 118void Pasteboard::clear() 119{ 120 [m_pasteboard.get() declareTypes:[NSArray array] owner:nil]; 121} 122 123static NSAttributedString *stripAttachmentCharacters(NSAttributedString *string) 124{ 125 const unichar attachmentCharacter = NSAttachmentCharacter; 126 DEFINE_STATIC_LOCAL(RetainPtr<NSString>, attachmentCharacterString, ([NSString stringWithCharacters:&attachmentCharacter length:1])); 127 NSMutableAttributedString *result = [[string mutableCopy] autorelease]; 128 NSRange attachmentRange = [[result string] rangeOfString:attachmentCharacterString.get()]; 129 while (attachmentRange.location != NSNotFound) { 130 [result replaceCharactersInRange:attachmentRange withString:@""]; 131 attachmentRange = [[result string] rangeOfString:attachmentCharacterString.get()]; 132 } 133 return result; 134} 135 136void Pasteboard::writeSelection(NSPasteboard* pasteboard, Range* selectedRange, bool canSmartCopyOrDelete, Frame* frame) 137{ 138 if (WebArchivePboardType == nil) 139 Pasteboard::generalPasteboard(); //Initialises pasteboard types 140 ASSERT(selectedRange); 141 142 NSAttributedString *attributedString = [[[NSAttributedString alloc] _initWithDOMRange:kit(selectedRange)] autorelease]; 143#ifdef BUILDING_ON_TIGER 144 // 4930197: Mail overrides [WebHTMLView pasteboardTypesForSelection] in order to add another type to the pasteboard 145 // after WebKit does. On Tiger we must call this function so that Mail code will be executed, meaning that 146 // we can't call WebCore::Pasteboard's method for setting types. 147 UNUSED_PARAM(canSmartCopyOrDelete); 148 149 NSArray *types = frame->editor()->client()->pasteboardTypesForSelection(frame); 150 // Don't write RTFD to the pasteboard when the copied attributed string has no attachments. 151 NSMutableArray *mutableTypes = nil; 152 if (![attributedString containsAttachments]) { 153 mutableTypes = [[types mutableCopy] autorelease]; 154 [mutableTypes removeObject:NSRTFDPboardType]; 155 types = mutableTypes; 156 } 157 [pasteboard declareTypes:types owner:nil]; 158#else 159 NSArray *types = selectionPasteboardTypes(canSmartCopyOrDelete, [attributedString containsAttachments]); 160 [pasteboard declareTypes:types owner:nil]; 161 frame->editor()->client()->didSetSelectionTypesForPasteboard(); 162#endif 163 164 // Put HTML on the pasteboard. 165 if ([types containsObject:WebArchivePboardType]) { 166 RefPtr<LegacyWebArchive> archive = LegacyWebArchive::createFromSelection(frame); 167 RetainPtr<CFDataRef> data = archive ? archive->rawDataRepresentation() : 0; 168 [pasteboard setData:(NSData *)data.get() forType:WebArchivePboardType]; 169 } 170 171 // Put the attributed string on the pasteboard (RTF/RTFD format). 172 if ([types containsObject:NSRTFDPboardType]) { 173 NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil]; 174 [pasteboard setData:RTFDData forType:NSRTFDPboardType]; 175 } 176 if ([types containsObject:NSRTFPboardType]) { 177 if ([attributedString containsAttachments]) 178 attributedString = stripAttachmentCharacters(attributedString); 179 NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil]; 180 [pasteboard setData:RTFData forType:NSRTFPboardType]; 181 } 182 183 // Put plain string on the pasteboard. 184 if ([types containsObject:NSStringPboardType]) { 185 // Map to a plain old space because this is better for source code, other browsers do it, 186 // and because HTML forces you to do this any time you want two spaces in a row. 187 String text = frame->displayStringModifiedByEncoding(selectedRange->text()); 188 NSMutableString *s = [[[(NSString*)text copy] autorelease] mutableCopy]; 189 190 NSString *NonBreakingSpaceString = [NSString stringWithCharacters:&noBreakSpace length:1]; 191 [s replaceOccurrencesOfString:NonBreakingSpaceString withString:@" " options:0 range:NSMakeRange(0, [s length])]; 192 [pasteboard setString:s forType:NSStringPboardType]; 193 [s release]; 194 } 195 196 if ([types containsObject:WebSmartPastePboardType]) { 197 [pasteboard setData:nil forType:WebSmartPastePboardType]; 198 } 199} 200 201void Pasteboard::writeSelection(Range* selectedRange, bool canSmartCopyOrDelete, Frame* frame) 202{ 203 Pasteboard::writeSelection(m_pasteboard.get(), selectedRange, canSmartCopyOrDelete, frame); 204} 205 206void Pasteboard::writeURL(NSPasteboard* pasteboard, NSArray* types, const KURL& url, const String& titleStr, Frame* frame) 207{ 208 if (WebArchivePboardType == nil) 209 Pasteboard::generalPasteboard(); //Initialises pasteboard types 210 211 if (types == nil) { 212 types = writableTypesForURL(); 213 [pasteboard declareTypes:types owner:nil]; 214 } 215 216 ASSERT(!url.isEmpty()); 217 218 NSURL *cocoaURL = url; 219 NSString *userVisibleString = frame->editor()->client()->userVisibleString(cocoaURL); 220 221 NSString *title = (NSString*)titleStr; 222 if ([title length] == 0) { 223 title = [[cocoaURL path] lastPathComponent]; 224 if ([title length] == 0) 225 title = userVisibleString; 226 } 227 228 if ([types containsObject:WebURLsWithTitlesPboardType]) 229 [pasteboard setPropertyList:[NSArray arrayWithObjects:[NSArray arrayWithObject:userVisibleString], 230 [NSArray arrayWithObject:(NSString*)titleStr.stripWhiteSpace()], 231 nil] 232 forType:WebURLsWithTitlesPboardType]; 233 if ([types containsObject:NSURLPboardType]) 234 [cocoaURL writeToPasteboard:pasteboard]; 235 if ([types containsObject:WebURLPboardType]) 236 [pasteboard setString:userVisibleString forType:WebURLPboardType]; 237 if ([types containsObject:WebURLNamePboardType]) 238 [pasteboard setString:title forType:WebURLNamePboardType]; 239 if ([types containsObject:NSStringPboardType]) 240 [pasteboard setString:userVisibleString forType:NSStringPboardType]; 241} 242 243void Pasteboard::writeURL(const KURL& url, const String& titleStr, Frame* frame) 244{ 245 Pasteboard::writeURL(m_pasteboard.get(), nil, url, titleStr, frame); 246} 247 248static NSFileWrapper* fileWrapperForImage(CachedResource* resource, NSURL *url) 249{ 250 SharedBuffer* coreData = resource->data(); 251 NSData *data = [[[NSData alloc] initWithBytes:coreData->data() length:coreData->size()] autorelease]; 252 NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:data] autorelease]; 253 String coreMIMEType = resource->response().mimeType(); 254 NSString *MIMEType = nil; 255 if (!coreMIMEType.isNull()) 256 MIMEType = coreMIMEType; 257 [wrapper setPreferredFilename:suggestedFilenameWithMIMEType(url, MIMEType)]; 258 return wrapper; 259} 260 261void Pasteboard::writeFileWrapperAsRTFDAttachment(NSFileWrapper* wrapper) 262{ 263 NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper:wrapper]; 264 265 NSAttributedString *string = [NSAttributedString attributedStringWithAttachment:attachment]; 266 [attachment release]; 267 268 NSData *RTFDData = [string RTFDFromRange:NSMakeRange(0, [string length]) documentAttributes:nil]; 269 [m_pasteboard.get() setData:RTFDData forType:NSRTFDPboardType]; 270} 271 272void Pasteboard::writeImage(Node* node, const KURL& url, const String& title) 273{ 274 ASSERT(node); 275 Frame* frame = node->document()->frame(); 276 277 NSURL *cocoaURL = url; 278 ASSERT(cocoaURL); 279 280 ASSERT(node->renderer() && node->renderer()->isImage()); 281 RenderImage* renderer = toRenderImage(node->renderer()); 282 CachedImage* cachedImage = renderer->cachedImage(); 283 ASSERT(cachedImage); 284 285 if (cachedImage->errorOccurred()) 286 return; 287 288 NSArray* types = writableTypesForImage(); 289 [m_pasteboard.get() declareTypes:types owner:nil]; 290 writeURL(m_pasteboard.get(), types, cocoaURL, nsStringNilIfEmpty(title), frame); 291 292 Image* image = cachedImage->image(); 293 ASSERT(image); 294 295 [m_pasteboard.get() setData:[image->getNSImage() TIFFRepresentation] forType:NSTIFFPboardType]; 296 297 String MIMEType = cachedImage->response().mimeType(); 298 ASSERT(MIMETypeRegistry::isSupportedImageResourceMIMEType(MIMEType)); 299 300 writeFileWrapperAsRTFDAttachment(fileWrapperForImage(cachedImage, cocoaURL)); 301} 302 303bool Pasteboard::canSmartReplace() 304{ 305 return [[m_pasteboard.get() types] containsObject:WebSmartPastePboardType]; 306} 307 308String Pasteboard::plainText(Frame* frame) 309{ 310 NSArray *types = [m_pasteboard.get() types]; 311 312 if ([types containsObject:NSStringPboardType]) 313 return [[m_pasteboard.get() stringForType:NSStringPboardType] precomposedStringWithCanonicalMapping]; 314 315 NSAttributedString *attributedString = nil; 316 NSString *string; 317 318 if ([types containsObject:NSRTFDPboardType]) 319 attributedString = [[NSAttributedString alloc] initWithRTFD:[m_pasteboard.get() dataForType:NSRTFDPboardType] documentAttributes:NULL]; 320 if (attributedString == nil && [types containsObject:NSRTFPboardType]) 321 attributedString = [[NSAttributedString alloc] initWithRTF:[m_pasteboard.get() dataForType:NSRTFPboardType] documentAttributes:NULL]; 322 if (attributedString != nil) { 323 string = [[attributedString string] precomposedStringWithCanonicalMapping]; 324 [attributedString release]; 325 return string; 326 } 327 328 if ([types containsObject:NSFilenamesPboardType]) { 329 string = [[[m_pasteboard.get() propertyListForType:NSFilenamesPboardType] componentsJoinedByString:@"\n"] precomposedStringWithCanonicalMapping]; 330 if (string != nil) 331 return string; 332 } 333 334 335 if (NSURL *url = [NSURL URLFromPasteboard:m_pasteboard.get()]) { 336 // FIXME: using the editorClient to call into webkit, for now, since 337 // calling _web_userVisibleString from WebCore involves migrating a sizable web of 338 // helper code that should either be done in a separate patch or figured out in another way. 339 string = frame->editor()->client()->userVisibleString(url); 340 if ([string length] > 0) 341 return [string precomposedStringWithCanonicalMapping]; 342 } 343 344 345 return String(); 346} 347 348PassRefPtr<DocumentFragment> Pasteboard::documentFragment(Frame* frame, PassRefPtr<Range> context, bool allowPlainText, bool& chosePlainText) 349{ 350 NSArray *types = [m_pasteboard.get() types]; 351 chosePlainText = false; 352 353 if ([types containsObject:NSHTMLPboardType]) { 354 NSString *HTMLString = [m_pasteboard.get() stringForType:NSHTMLPboardType]; 355 // This is a hack to make Microsoft's HTML pasteboard data work. See 3778785. 356 if ([HTMLString hasPrefix:@"Version:"]) { 357 NSRange range = [HTMLString rangeOfString:@"<html" options:NSCaseInsensitiveSearch]; 358 if (range.location != NSNotFound) { 359 HTMLString = [HTMLString substringFromIndex:range.location]; 360 } 361 } 362 if ([HTMLString length] != 0) { 363 RefPtr<DocumentFragment> fragment = createFragmentFromMarkup(frame->document(), HTMLString, ""); 364 if (fragment) 365 return fragment.release(); 366 } 367 } 368 369 if (allowPlainText && [types containsObject:NSStringPboardType]) { 370 chosePlainText = true; 371 RefPtr<DocumentFragment> fragment = createFragmentFromText(context.get(), [[m_pasteboard.get() stringForType:NSStringPboardType] precomposedStringWithCanonicalMapping]); 372 if (fragment) 373 return fragment.release(); 374 } 375 376 return 0; 377} 378 379} 380