1/* 2 * Copyright (C) 2007, 2008, 2012 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 INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY 17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 */ 24 25#import "config.h" 26#import "Pasteboard.h" 27 28#import "CachedImage.h" 29#import "DOMRangeInternal.h" 30#import "Document.h" 31#import "DocumentFragment.h" 32#import "DocumentLoader.h" 33#import "Editor.h" 34#import "EditorClient.h" 35#import "Frame.h" 36#import "FrameLoader.h" 37#import "FrameLoaderClient.h" 38#import "HTMLElement.h" 39#import "HTMLNames.h" 40#import "HTMLParserIdioms.h" 41#import "KURL.h" 42#import "LegacyWebArchive.h" 43#import "Page.h" 44#import "RenderImage.h" 45#import "SoftLinking.h" 46#import "Text.h" 47#import "htmlediting.h" 48#import "markup.h" 49#import "WebNSAttributedStringExtras.h" 50#import <MobileCoreServices/MobileCoreServices.h> 51 52@interface NSHTMLReader 53- (id)initWithDOMRange:(DOMRange *)domRange; 54- (NSAttributedString *)attributedString; 55@end 56 57@interface NSAttributedString (NSAttributedStringKitAdditions) 58- (id)initWithRTF:(NSData *)data documentAttributes:(NSDictionary **)dict; 59- (id)initWithRTFD:(NSData *)data documentAttributes:(NSDictionary **)dict; 60- (NSData *)RTFFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict; 61- (NSData *)RTFDFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict; 62- (BOOL)containsAttachments; 63@end 64 65SOFT_LINK_PRIVATE_FRAMEWORK(UIFoundation) 66SOFT_LINK_CLASS(UIFoundation, NSHTMLReader) 67 68SOFT_LINK_FRAMEWORK(MobileCoreServices) 69 70SOFT_LINK(MobileCoreServices, UTTypeConformsTo, Boolean, (CFStringRef inUTI, CFStringRef inConformsToUTI), (inUTI, inConformsToUTI)) 71SOFT_LINK(MobileCoreServices, UTTypeCreatePreferredIdentifierForTag, CFStringRef, (CFStringRef inTagClass, CFStringRef inTag, CFStringRef inConformingToUTI), (inTagClass, inTag, inConformingToUTI)) 72SOFT_LINK(MobileCoreServices, UTTypeCopyPreferredTagWithClass, CFStringRef, (CFStringRef inUTI, CFStringRef inTagClass), (inUTI, inTagClass)) 73 74SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeText, CFStringRef) 75SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypePNG, CFStringRef) 76SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeJPEG, CFStringRef) 77SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeURL, CFStringRef) 78SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeTIFF, CFStringRef) 79SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeGIF, CFStringRef) 80SOFT_LINK_CONSTANT(MobileCoreServices, kUTTagClassMIMEType, CFStringRef) 81SOFT_LINK_CONSTANT(MobileCoreServices, kUTTagClassFilenameExtension, CFStringRef) 82SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeRTFD, CFStringRef) 83SOFT_LINK_CONSTANT(MobileCoreServices, kUTTypeRTF, CFStringRef) 84 85#define kUTTypeText getkUTTypeText() 86#define kUTTypePNG getkUTTypePNG() 87#define kUTTypeJPEG getkUTTypeJPEG() 88#define kUTTypeURL getkUTTypeURL() 89#define kUTTypeTIFF getkUTTypeTIFF() 90#define kUTTypeGIF getkUTTypeGIF() 91#define kUTTagClassMIMEType getkUTTagClassMIMEType() 92#define kUTTagClassFilenameExtension getkUTTagClassFilenameExtension() 93#define kUTTypeRTFD getkUTTypeRTFD() 94#define kUTTypeRTF getkUTTypeRTF() 95 96SOFT_LINK_FRAMEWORK(AppSupport) 97SOFT_LINK(AppSupport, CPSharedResourcesDirectory, CFStringRef, (void), ()) 98 99namespace WebCore { 100 101NSString *WebArchivePboardType = @"Apple Web Archive pasteboard type"; 102 103Pasteboard* Pasteboard::generalPasteboard() 104{ 105 static Pasteboard* pasteboard = new Pasteboard(); 106 return pasteboard; 107} 108 109Pasteboard::Pasteboard() 110{ 111} 112 113void Pasteboard::clear() 114{ 115} 116 117void Pasteboard::writeSelection(Range* selectedRange, bool /*canSmartCopyOrDelete*/, Frame *frame, ShouldSerializeSelectedTextForClipboard shouldSerializeSelectedTextForClipboard) 118{ 119 ASSERT(selectedRange); 120 ASSERT(frame); 121 122 // If the selection is at the beginning of content inside an anchor tag 123 // we move the selection start to include the anchor. 124 ExceptionCode ec; 125 Node* commonAncestor = selectedRange->commonAncestorContainer(ec); 126 ASSERT(commonAncestor); 127 Node* enclosingAnchor = enclosingNodeWithTag(firstPositionInNode(commonAncestor), HTMLNames::aTag); 128 if (enclosingAnchor && comparePositions(firstPositionInOrBeforeNode(selectedRange->startPosition().anchorNode()), selectedRange->startPosition()) >= 0) 129 selectedRange->setStart(enclosingAnchor, 0, ec); 130 131 frame->editor().client()->didSetSelectionTypesForPasteboard(); 132 133 RetainPtr<NSDictionary> representations = adoptNS([[NSMutableDictionary alloc] init]); 134 135 // Put WebArchive on the pasteboard. 136 RefPtr<LegacyWebArchive> archive = LegacyWebArchive::createFromSelection(frame); 137 RetainPtr<CFDataRef> data = archive ? archive->rawDataRepresentation() : 0; 138 if (data) 139 [representations.get() setValue:(NSData *)data.get() forKey:WebArchivePboardType]; 140 141 RetainPtr<NSHTMLReader> converter = adoptNS([[getNSHTMLReaderClass() alloc] initWithDOMRange:kit(selectedRange)]); 142 if (converter) { 143 NSAttributedString *attributedString = [converter.get() attributedString]; 144 NSData* RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil]; 145 if (RTFDData) 146 [representations.get() setValue:RTFDData forKey:(NSString *)kUTTypeRTFD]; 147 if ([attributedString containsAttachments]) 148 attributedString = attributedStringByStrippingAttachmentCharacters(attributedString); 149 NSData* RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil]; 150 if (RTFData) 151 [representations.get() setValue:RTFData forKey:(NSString *)kUTTypeRTF]; 152 } 153 154 // Put plain string on the pasteboard. 155 String text = shouldSerializeSelectedTextForClipboard == IncludeImageAltTextForClipboard 156 ? frame->editor().selectedTextForClipboard() : frame->editor().selectedText(); 157 text.replace(noBreakSpace, ' '); 158 [representations.get() setValue:text forKey:(NSString *)kUTTypeText]; 159 160 frame->editor().client()->writeDataToPasteboard(representations.get()); 161} 162 163void Pasteboard::writePlainText(const String& text, Frame *frame) 164{ 165 ASSERT(frame); 166 167 RetainPtr<NSDictionary> representations = adoptNS([[NSMutableDictionary alloc] init]); 168 [representations.get() setValue:text forKey:(NSString *)kUTTypeText]; 169 frame->editor().client()->writeDataToPasteboard(representations.get()); 170} 171 172void Pasteboard::writeImage(Node* node, Frame* frame) 173{ 174 ASSERT(node); 175 176 if (!(node->renderer() && node->renderer()->isImage())) 177 return; 178 179 RenderImage* renderer = toRenderImage(node->renderer()); 180 CachedImage* cachedImage = renderer->cachedImage(); 181 if (!cachedImage || cachedImage->errorOccurred()) 182 return; 183 184 Image* image = cachedImage->imageForRenderer(renderer); 185 ASSERT(image); 186 187 RetainPtr<NSData> imageData = image->data()->createNSData(); 188 189 if (!imageData) 190 return; 191 192 RetainPtr<NSMutableDictionary> dictionary = adoptNS([[NSMutableDictionary alloc] init]); 193 NSString *mimeType = cachedImage->response().mimeType(); 194 RetainPtr<CFStringRef> uti = adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (CFStringRef)mimeType, NULL)); 195 if (uti) { 196 [dictionary.get() setObject:imageData.get() forKey:(NSString *)uti.get()]; 197 [dictionary.get() setObject:(NSString *)node->document()->completeURL(stripLeadingAndTrailingHTMLSpaces(static_cast<HTMLElement*>(node)->getAttribute("src"))) forKey:(NSString *)kUTTypeURL]; 198 } 199 frame->editor().client()->writeDataToPasteboard(dictionary.get()); 200} 201 202void Pasteboard::writePlainText(const String&, SmartReplaceOption) 203{ 204} 205 206void Pasteboard::writeClipboard(Clipboard*) 207{ 208} 209 210bool Pasteboard::canSmartReplace() 211{ 212 return false; 213} 214 215String Pasteboard::plainText(Frame* frame) 216{ 217 RetainPtr<NSArray> pasteboardItem = frame->editor().client()->readDataFromPasteboard((NSString *)kUTTypeText, 0); 218 219 if ([pasteboardItem.get() count] == 0) 220 return String(); 221 222 id value = [pasteboardItem.get() objectAtIndex:0]; 223 if ([value isKindOfClass:[NSString class]]) 224 return String(value); 225 226 ASSERT([value isKindOfClass:[NSString class]]); 227 return String(); 228} 229 230static NSArray* supportedImageTypes() 231{ 232 return [NSArray arrayWithObjects:(id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF, nil]; 233} 234 235NSArray* Pasteboard::supportedPasteboardTypes() 236{ 237 return [NSArray arrayWithObjects:(id)WebArchivePboardType, (id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF, (id)kUTTypeURL, (id)kUTTypeText, (id)kUTTypeRTFD, (id)kUTTypeRTF, nil]; 238} 239 240#define WebDataProtocolScheme @"webkit-fake-url" 241 242static NSURL* uniqueURLWithRelativePart(NSString *relativePart) 243{ 244 CFUUIDRef UUIDRef = CFUUIDCreate(kCFAllocatorDefault); 245 NSString *UUIDString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef); 246 CFRelease(UUIDRef); 247 NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@/%@", WebDataProtocolScheme, UUIDString, relativePart]]; 248 CFRelease(UUIDString); 249 250 return URL; 251} 252 253static PassRefPtr<DocumentFragment> documentFragmentWithImageResource(Frame* frame, PassRefPtr<ArchiveResource> resource) 254{ 255 RefPtr<Element> imageElement = frame->document()->createElement(HTMLNames::imgTag, false); 256 257 if (DocumentLoader* loader = frame->loader()->documentLoader()) 258 loader->addArchiveResource(resource.get()); 259 260 NSURL *URL = resource->url(); 261 imageElement->setAttribute(HTMLNames::srcAttr, [URL isFileURL] ? [URL absoluteString] : resource->url()); 262 RefPtr<DocumentFragment> fragment = frame->document()->createDocumentFragment(); 263 fragment->appendChild(imageElement.release()); 264 return fragment.release(); 265} 266 267static PassRefPtr<DocumentFragment> documentFragmentWithLink(Document* document, const String& urlString) 268{ 269 RefPtr<Element> anchorElement = document->createElement(HTMLNames::aTag, false); 270 271 anchorElement->setAttribute(HTMLNames::hrefAttr, urlString); 272 anchorElement->appendChild(document->createTextNode(urlString)); 273 274 RefPtr<DocumentFragment> fragment = document->createDocumentFragment(); 275 fragment->appendChild(anchorElement.release()); 276 return fragment.release(); 277} 278 279static PassRefPtr<DocumentFragment> documentFragmentWithRTF(Frame* frame, NSString *pasteboardType, NSData* pasteboardData) 280{ 281 if (!frame || !frame->document() || !frame->document()->isHTMLDocument()) 282 return 0; 283 284 RetainPtr<NSAttributedString> string; 285 if ([pasteboardType isEqualToString:(NSString*)kUTTypeRTFD]) 286 string = [[NSAttributedString alloc] initWithRTFD:pasteboardData documentAttributes:NULL]; 287 288 if (!string) 289 string = [[NSAttributedString alloc] initWithRTF:pasteboardData documentAttributes:NULL]; 290 291 if (!string) 292 return 0; 293 294 bool wasDeferringCallbacks = frame->page()->defersLoading(); 295 if (!wasDeferringCallbacks) 296 frame->page()->setDefersLoading(true); 297 298 Vector<RefPtr<ArchiveResource> > resources; 299 RefPtr<DocumentFragment> fragment = frame->editor().client()->documentFragmentFromAttributedString(string.get(), resources); 300 301 size_t size = resources.size(); 302 if (size) { 303 DocumentLoader* loader = frame->loader()->documentLoader(); 304 for (size_t i = 0; i < size; ++i) 305 loader->addArchiveResource(resources[i]); 306 } 307 308 if (!wasDeferringCallbacks) 309 frame->page()->setDefersLoading(false); 310 311 return fragment.release(); 312} 313 314PassRefPtr<DocumentFragment> Pasteboard::documentFragmentForPasteboardItemAtIndex(Frame* frame, int index, bool allowPlainText, bool& chosePlainText) 315{ 316 RefPtr<DocumentFragment> fragment = frame->editor().client()->documentFragmentFromDelegate(index); 317 if (fragment) 318 return fragment.release(); 319 320 // First try to ask the client about the supported types. It will return null if the client 321 // has no selection. 322 NSArray *supportedTypes = frame->editor().client()->supportedPasteboardTypesForCurrentSelection(); 323 if (!supportedTypes) 324 supportedTypes = supportedPasteboardTypes(); 325 int numberOfTypes = [supportedTypes count]; 326 327 for (int i = 0; i < numberOfTypes; i++) { 328 NSString *type = [supportedTypes objectAtIndex:i]; 329 RetainPtr<NSArray> pasteboardItem = frame->editor().client()->readDataFromPasteboard(type, index); 330 331 if ([pasteboardItem.get() count] == 0) 332 continue; 333 334 if ([type isEqualToString:WebArchivePboardType]) { 335 if (!frame->document()) 336 return 0; 337 338 // We put [WebArchive data] on the pasteboard in -copy: instead of the archive itself until there is API to provide the WebArchive. 339 NSData *data = [pasteboardItem.get() objectAtIndex:0]; 340 RefPtr<LegacyWebArchive> coreArchive = LegacyWebArchive::create(SharedBuffer::wrapNSData(data).get()); 341 if (coreArchive) { 342 RefPtr<ArchiveResource> mainResource = coreArchive->mainResource(); 343 if (mainResource) { 344 NSString *MIMEType = mainResource->mimeType(); 345 if (frame->loader()->client()->canShowMIMETypeAsHTML(MIMEType)) { 346 RetainPtr<NSString> markupString = adoptNS([[NSString alloc] initWithData:[mainResource->data()->createNSData() autorelease] encoding:NSUTF8StringEncoding]); 347 if (DocumentLoader* loader = frame->loader()->documentLoader()) 348 loader->addAllArchiveResources(coreArchive.get()); 349 350 fragment = createFragmentFromMarkup(frame->document(), markupString.get(), mainResource->url(), DisallowScriptingContent); 351 } 352 } 353 if (fragment) 354 return fragment.release(); 355 } 356 } 357 358 if ([type isEqualToString:(NSString *)kUTTypeRTFD]) 359 return documentFragmentWithRTF(frame, (NSString *)kUTTypeRTFD, [pasteboardItem.get() objectAtIndex:0]); 360 361 if ([type isEqualToString:(NSString *)kUTTypeRTF]) 362 return documentFragmentWithRTF(frame, (NSString *)kUTTypeRTF, [pasteboardItem.get() objectAtIndex:0]); 363 364 if ([supportedImageTypes() containsObject:type]) { 365 RetainPtr<NSString> filenameExtension = adoptNS((NSString *)UTTypeCopyPreferredTagWithClass((CFStringRef)type, kUTTagClassFilenameExtension)); 366 NSString *relativeURLPart = [@"image" stringByAppendingString:filenameExtension.get()]; 367 RetainPtr<NSString> mimeType = adoptNS((NSString *)UTTypeCopyPreferredTagWithClass((CFStringRef)type, kUTTagClassMIMEType)); 368 NSData *data = [pasteboardItem.get() objectAtIndex:0]; 369 return documentFragmentWithImageResource(frame, ArchiveResource::create(SharedBuffer::wrapNSData([[data copy] autorelease]), uniqueURLWithRelativePart(relativeURLPart), mimeType.get(), "", "")); 370 } 371 if ([type isEqualToString:(NSString *)kUTTypeURL]) { 372 id value = [pasteboardItem.get() objectAtIndex:0]; 373 if (![value isKindOfClass:[NSURL class]]) { 374 ASSERT([value isKindOfClass:[NSURL class]]); 375 return 0; 376 } 377 NSURL *url = (NSURL *)value; 378 379 if (!frame->editor().client()->hasRichlyEditableSelection()) { 380 fragment = createFragmentFromText(frame->selection()->toNormalizedRange().get(), [url absoluteString]); 381 if (fragment) 382 return fragment.release(); 383 } 384 385 if ([url isFileURL]) { 386 NSString *localPath = [url relativePath]; 387 // Only allow url attachments from ~/Media for now. 388 if (![localPath hasPrefix:[(NSString *)CPSharedResourcesDirectory() stringByAppendingString:@"/Media/DCIM/"]]) 389 continue; 390 391 RetainPtr<NSString> fileType = adoptNS((NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[localPath pathExtension], NULL)); 392 NSData *data = [NSData dataWithContentsOfFile:localPath]; 393 if (UTTypeConformsTo((CFStringRef)fileType.get(), kUTTypePNG)) 394 return documentFragmentWithImageResource(frame, ArchiveResource::create(SharedBuffer::wrapNSData([[data copy] autorelease]), uniqueURLWithRelativePart(@"image.png"), @"image/png", "", "")); 395 else if (UTTypeConformsTo((CFStringRef)fileType.get(), kUTTypeJPEG)) 396 return documentFragmentWithImageResource(frame, ArchiveResource::create(SharedBuffer::wrapNSData([[data copy] autorelease]), uniqueURLWithRelativePart(@"image.jpg"), @"image/jpg", "", "")); 397 } else { 398 // Create a link with URL text. 399 return documentFragmentWithLink(frame->document(), [url absoluteString]); 400 } 401 } 402 if (allowPlainText && [type isEqualToString:(NSString *)kUTTypeText]) { 403 id value = [pasteboardItem.get() objectAtIndex:0]; 404 if (![value isKindOfClass:[NSString class]]) { 405 ASSERT([value isKindOfClass:[NSString class]]); 406 return 0; 407 } 408 409 chosePlainText = true; 410 fragment = createFragmentFromText(frame->selection()->toNormalizedRange().get(), (NSString*)value); 411 if (fragment) 412 return fragment.release(); 413 } 414 } 415 416 return 0; 417} 418 419PassRefPtr<DocumentFragment> Pasteboard::documentFragment(Frame* frame, PassRefPtr<Range> /*context*/, bool allowPlainText, bool& chosePlainText) 420{ 421 chosePlainText = false; 422 423 if (!frame) 424 return 0; 425 426 int numberOfItems = frame->editor().client()->getPasteboardItemsCount(); 427 428 if (!numberOfItems) 429 return 0; 430 431 // In the common case there is just one item on the pasteboard, avoid the expense of transferring the content of 432 // fragmentForCurrentItem to the main fragment. 433 RefPtr<DocumentFragment> fragment = documentFragmentForPasteboardItemAtIndex(frame, 0, allowPlainText, chosePlainText); 434 435 for (int i = 1; i < numberOfItems; i++) { 436 RefPtr<DocumentFragment> fragmentForCurrentItem = documentFragmentForPasteboardItemAtIndex(frame, i, allowPlainText, chosePlainText); 437 if (!fragment) 438 fragment = fragmentForCurrentItem; 439 else if (fragmentForCurrentItem && fragmentForCurrentItem->firstChild()) { 440 ExceptionCode ec; 441 fragment->appendChild(fragmentForCurrentItem->firstChild(), ec); 442 } 443 } 444 445 if (fragment) 446 return fragment.release(); 447 448 return 0; 449} 450 451} 452