1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ui/base/clipboard/clipboard.h" 6 7 #import <Cocoa/Cocoa.h> 8 9 #include "base/basictypes.h" 10 #include "base/files/file_path.h" 11 #include "base/logging.h" 12 #include "base/mac/mac_util.h" 13 #include "base/mac/scoped_cftyperef.h" 14 #import "base/mac/scoped_nsexception_enabler.h" 15 #include "base/mac/scoped_nsobject.h" 16 #include "base/stl_util.h" 17 #include "base/strings/sys_string_conversions.h" 18 #include "base/strings/utf_string_conversions.h" 19 #include "skia/ext/skia_utils_mac.h" 20 #import "third_party/mozilla/NSPasteboard+Utils.h" 21 #include "third_party/skia/include/core/SkBitmap.h" 22 #include "ui/base/clipboard/custom_data_helper.h" 23 #include "ui/gfx/canvas.h" 24 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" 25 #include "ui/gfx/size.h" 26 27 namespace ui { 28 29 namespace { 30 31 // Would be nice if this were in UTCoreTypes.h, but it isn't 32 NSString* const kUTTypeURLName = @"public.url-name"; 33 34 // Tells us if WebKit was the last to write to the pasteboard. There's no 35 // actual data associated with this type. 36 NSString* const kWebSmartPastePboardType = @"NeXT smart paste pasteboard type"; 37 38 // Pepper custom data format type. 39 NSString* const kPepperCustomDataPboardType = 40 @"org.chromium.pepper-custom-data"; 41 42 NSPasteboard* GetPasteboard() { 43 // The pasteboard should not be nil in a UI session, but this handy DCHECK 44 // can help track down problems if someone tries using clipboard code outside 45 // of a UI session. 46 NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; 47 DCHECK(pasteboard); 48 return pasteboard; 49 } 50 51 } // namespace 52 53 Clipboard::FormatType::FormatType() : data_(nil) { 54 } 55 56 Clipboard::FormatType::FormatType(NSString* native_format) 57 : data_([native_format retain]) { 58 } 59 60 Clipboard::FormatType::FormatType(const FormatType& other) 61 : data_([other.data_ retain]) { 62 } 63 64 Clipboard::FormatType& Clipboard::FormatType::operator=( 65 const FormatType& other) { 66 if (this != &other) { 67 [data_ release]; 68 data_ = [other.data_ retain]; 69 } 70 return *this; 71 } 72 73 bool Clipboard::FormatType::Equals(const FormatType& other) const { 74 return [data_ isEqualToString:other.data_]; 75 } 76 77 Clipboard::FormatType::~FormatType() { 78 [data_ release]; 79 } 80 81 std::string Clipboard::FormatType::Serialize() const { 82 return base::SysNSStringToUTF8(data_); 83 } 84 85 // static 86 Clipboard::FormatType Clipboard::FormatType::Deserialize( 87 const std::string& serialization) { 88 return FormatType(base::SysUTF8ToNSString(serialization)); 89 } 90 91 Clipboard::Clipboard() { 92 DCHECK(CalledOnValidThread()); 93 } 94 95 Clipboard::~Clipboard() { 96 DCHECK(CalledOnValidThread()); 97 } 98 99 void Clipboard::WriteObjects(ClipboardType type, const ObjectMap& objects) { 100 DCHECK(CalledOnValidThread()); 101 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 102 103 NSPasteboard* pb = GetPasteboard(); 104 [pb declareTypes:[NSArray array] owner:nil]; 105 106 for (ObjectMap::const_iterator iter = objects.begin(); 107 iter != objects.end(); ++iter) { 108 DispatchObject(static_cast<ObjectType>(iter->first), iter->second); 109 } 110 } 111 112 void Clipboard::WriteText(const char* text_data, size_t text_len) { 113 std::string text_str(text_data, text_len); 114 NSString *text = base::SysUTF8ToNSString(text_str); 115 NSPasteboard* pb = GetPasteboard(); 116 [pb addTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; 117 [pb setString:text forType:NSStringPboardType]; 118 } 119 120 void Clipboard::WriteHTML(const char* markup_data, 121 size_t markup_len, 122 const char* url_data, 123 size_t url_len) { 124 // We need to mark it as utf-8. (see crbug.com/11957) 125 std::string html_fragment_str("<meta charset='utf-8'>"); 126 html_fragment_str.append(markup_data, markup_len); 127 NSString *html_fragment = base::SysUTF8ToNSString(html_fragment_str); 128 129 // TODO(avi): url_data? 130 NSPasteboard* pb = GetPasteboard(); 131 [pb addTypes:[NSArray arrayWithObject:NSHTMLPboardType] owner:nil]; 132 [pb setString:html_fragment forType:NSHTMLPboardType]; 133 } 134 135 void Clipboard::WriteRTF(const char* rtf_data, size_t data_len) { 136 WriteData(GetRtfFormatType(), rtf_data, data_len); 137 } 138 139 void Clipboard::WriteBookmark(const char* title_data, 140 size_t title_len, 141 const char* url_data, 142 size_t url_len) { 143 std::string title_str(title_data, title_len); 144 NSString *title = base::SysUTF8ToNSString(title_str); 145 std::string url_str(url_data, url_len); 146 NSString *url = base::SysUTF8ToNSString(url_str); 147 148 // TODO(playmobil): In the Windows version of this function, an HTML 149 // representation of the bookmark is also added to the clipboard, to support 150 // drag and drop of web shortcuts. I don't think we need to do this on the 151 // Mac, but we should double check later on. 152 NSURL* nsurl = [NSURL URLWithString:url]; 153 154 NSPasteboard* pb = GetPasteboard(); 155 // passing UTIs into the pasteboard methods is valid >= 10.5 156 [pb addTypes:[NSArray arrayWithObjects:NSURLPboardType, 157 kUTTypeURLName, 158 nil] 159 owner:nil]; 160 [nsurl writeToPasteboard:pb]; 161 [pb setString:title forType:kUTTypeURLName]; 162 } 163 164 void Clipboard::WriteBitmap(const SkBitmap& bitmap) { 165 NSImage* image = gfx::SkBitmapToNSImageWithColorSpace( 166 bitmap, base::mac::GetSystemColorSpace()); 167 // An API to ask the NSImage to write itself to the clipboard comes in 10.6 :( 168 // For now, spit out the image as a TIFF. 169 NSPasteboard* pb = GetPasteboard(); 170 [pb addTypes:[NSArray arrayWithObject:NSTIFFPboardType] owner:nil]; 171 NSData *tiff_data = [image TIFFRepresentation]; 172 LOG_IF(ERROR, tiff_data == NULL) << "Failed to allocate image for clipboard"; 173 if (tiff_data) { 174 [pb setData:tiff_data forType:NSTIFFPboardType]; 175 } 176 } 177 178 void Clipboard::WriteData(const FormatType& format, 179 const char* data_data, 180 size_t data_len) { 181 NSPasteboard* pb = GetPasteboard(); 182 [pb addTypes:[NSArray arrayWithObject:format.ToNSString()] owner:nil]; 183 [pb setData:[NSData dataWithBytes:data_data length:data_len] 184 forType:format.ToNSString()]; 185 } 186 187 // Write an extra flavor that signifies WebKit was the last to modify the 188 // pasteboard. This flavor has no data. 189 void Clipboard::WriteWebSmartPaste() { 190 NSPasteboard* pb = GetPasteboard(); 191 NSString* format = GetWebKitSmartPasteFormatType().ToNSString(); 192 [pb addTypes:[NSArray arrayWithObject:format] owner:nil]; 193 [pb setData:nil forType:format]; 194 } 195 196 uint64 Clipboard::GetSequenceNumber(ClipboardType type) { 197 DCHECK(CalledOnValidThread()); 198 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 199 200 NSPasteboard* pb = GetPasteboard(); 201 return [pb changeCount]; 202 } 203 204 bool Clipboard::IsFormatAvailable(const FormatType& format, 205 ClipboardType type) const { 206 DCHECK(CalledOnValidThread()); 207 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 208 209 NSPasteboard* pb = GetPasteboard(); 210 NSArray* types = [pb types]; 211 212 // Safari only places RTF on the pasteboard, never HTML. We can convert RTF 213 // to HTML, so the presence of either indicates success when looking for HTML. 214 if ([format.ToNSString() isEqualToString:NSHTMLPboardType]) { 215 return [types containsObject:NSHTMLPboardType] || 216 [types containsObject:NSRTFPboardType]; 217 } 218 return [types containsObject:format.ToNSString()]; 219 } 220 221 void Clipboard::Clear(ClipboardType type) { 222 DCHECK(CalledOnValidThread()); 223 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 224 225 NSPasteboard* pb = GetPasteboard(); 226 [pb declareTypes:[NSArray array] owner:nil]; 227 } 228 229 void Clipboard::ReadAvailableTypes(ClipboardType type, 230 std::vector<base::string16>* types, 231 bool* contains_filenames) const { 232 DCHECK(CalledOnValidThread()); 233 types->clear(); 234 if (IsFormatAvailable(Clipboard::GetPlainTextFormatType(), type)) 235 types->push_back(base::UTF8ToUTF16(kMimeTypeText)); 236 if (IsFormatAvailable(Clipboard::GetHtmlFormatType(), type)) 237 types->push_back(base::UTF8ToUTF16(kMimeTypeHTML)); 238 if (IsFormatAvailable(Clipboard::GetRtfFormatType(), type)) 239 types->push_back(base::UTF8ToUTF16(kMimeTypeRTF)); 240 if ([NSImage canInitWithPasteboard:GetPasteboard()]) 241 types->push_back(base::UTF8ToUTF16(kMimeTypePNG)); 242 *contains_filenames = false; 243 244 NSPasteboard* pb = GetPasteboard(); 245 if ([[pb types] containsObject:kWebCustomDataPboardType]) { 246 NSData* data = [pb dataForType:kWebCustomDataPboardType]; 247 if ([data length]) 248 ReadCustomDataTypes([data bytes], [data length], types); 249 } 250 } 251 252 void Clipboard::ReadText(ClipboardType type, base::string16* result) const { 253 DCHECK(CalledOnValidThread()); 254 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 255 NSPasteboard* pb = GetPasteboard(); 256 NSString* contents = [pb stringForType:NSStringPboardType]; 257 258 *result = base::SysNSStringToUTF16(contents); 259 } 260 261 void Clipboard::ReadAsciiText(ClipboardType type, std::string* result) const { 262 DCHECK(CalledOnValidThread()); 263 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 264 NSPasteboard* pb = GetPasteboard(); 265 NSString* contents = [pb stringForType:NSStringPboardType]; 266 267 if (!contents) 268 result->clear(); 269 else 270 result->assign([contents UTF8String]); 271 } 272 273 void Clipboard::ReadHTML(ClipboardType type, 274 base::string16* markup, 275 std::string* src_url, 276 uint32* fragment_start, 277 uint32* fragment_end) const { 278 DCHECK(CalledOnValidThread()); 279 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 280 281 // TODO(avi): src_url? 282 markup->clear(); 283 if (src_url) 284 src_url->clear(); 285 286 NSPasteboard* pb = GetPasteboard(); 287 NSArray* supportedTypes = [NSArray arrayWithObjects:NSHTMLPboardType, 288 NSRTFPboardType, 289 NSStringPboardType, 290 nil]; 291 NSString* bestType = [pb availableTypeFromArray:supportedTypes]; 292 if (bestType) { 293 NSString* contents = [pb stringForType:bestType]; 294 if ([bestType isEqualToString:NSRTFPboardType]) 295 contents = [pb htmlFromRtf]; 296 *markup = base::SysNSStringToUTF16(contents); 297 } 298 299 *fragment_start = 0; 300 DCHECK(markup->length() <= kuint32max); 301 *fragment_end = static_cast<uint32>(markup->length()); 302 } 303 304 void Clipboard::ReadRTF(ClipboardType type, std::string* result) const { 305 DCHECK(CalledOnValidThread()); 306 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 307 308 return ReadData(GetRtfFormatType(), result); 309 } 310 311 SkBitmap Clipboard::ReadImage(ClipboardType type) const { 312 DCHECK(CalledOnValidThread()); 313 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); 314 315 // If the pasteboard's image data is not to its liking, the guts of NSImage 316 // may throw, and that exception will leak. Prevent a crash in that case; 317 // a blank image is better. 318 base::scoped_nsobject<NSImage> image(base::mac::RunBlockIgnoringExceptions(^{ 319 return [[NSImage alloc] initWithPasteboard:GetPasteboard()]; 320 })); 321 SkBitmap bitmap; 322 if (image.get()) { 323 bitmap = gfx::NSImageToSkBitmapWithColorSpace( 324 image.get(), /*is_opaque=*/ false, base::mac::GetSystemColorSpace()); 325 } 326 return bitmap; 327 } 328 329 void Clipboard::ReadCustomData(ClipboardType clipboard_type, 330 const base::string16& type, 331 base::string16* result) const { 332 DCHECK(CalledOnValidThread()); 333 DCHECK_EQ(clipboard_type, CLIPBOARD_TYPE_COPY_PASTE); 334 335 NSPasteboard* pb = GetPasteboard(); 336 if ([[pb types] containsObject:kWebCustomDataPboardType]) { 337 NSData* data = [pb dataForType:kWebCustomDataPboardType]; 338 if ([data length]) 339 ReadCustomDataForType([data bytes], [data length], type, result); 340 } 341 } 342 343 void Clipboard::ReadBookmark(base::string16* title, std::string* url) const { 344 DCHECK(CalledOnValidThread()); 345 NSPasteboard* pb = GetPasteboard(); 346 347 if (title) { 348 NSString* contents = [pb stringForType:kUTTypeURLName]; 349 *title = base::SysNSStringToUTF16(contents); 350 } 351 352 if (url) { 353 NSString* url_string = [[NSURL URLFromPasteboard:pb] absoluteString]; 354 if (!url_string) 355 url->clear(); 356 else 357 url->assign([url_string UTF8String]); 358 } 359 } 360 361 void Clipboard::ReadData(const FormatType& format, std::string* result) const { 362 DCHECK(CalledOnValidThread()); 363 NSPasteboard* pb = GetPasteboard(); 364 NSData* data = [pb dataForType:format.ToNSString()]; 365 if ([data length]) 366 result->assign(static_cast<const char*>([data bytes]), [data length]); 367 } 368 369 // static 370 Clipboard::FormatType Clipboard::GetFormatType( 371 const std::string& format_string) { 372 return FormatType::Deserialize(format_string); 373 } 374 375 // static 376 const Clipboard::FormatType& Clipboard::GetUrlFormatType() { 377 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSURLPboardType)); 378 return type; 379 } 380 381 // static 382 const Clipboard::FormatType& Clipboard::GetUrlWFormatType() { 383 return GetUrlFormatType(); 384 } 385 386 // static 387 const Clipboard::FormatType& Clipboard::GetPlainTextFormatType() { 388 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSStringPboardType)); 389 return type; 390 } 391 392 // static 393 const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType() { 394 return GetPlainTextFormatType(); 395 } 396 397 // static 398 const Clipboard::FormatType& Clipboard::GetFilenameFormatType() { 399 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSFilenamesPboardType)); 400 return type; 401 } 402 403 // static 404 const Clipboard::FormatType& Clipboard::GetFilenameWFormatType() { 405 return GetFilenameFormatType(); 406 } 407 408 // static 409 const Clipboard::FormatType& Clipboard::GetHtmlFormatType() { 410 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSHTMLPboardType)); 411 return type; 412 } 413 414 // static 415 const Clipboard::FormatType& Clipboard::GetRtfFormatType() { 416 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSRTFPboardType)); 417 return type; 418 } 419 420 // static 421 const Clipboard::FormatType& Clipboard::GetBitmapFormatType() { 422 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSTIFFPboardType)); 423 return type; 424 } 425 426 // static 427 const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType() { 428 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebSmartPastePboardType)); 429 return type; 430 } 431 432 // static 433 const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() { 434 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebCustomDataPboardType)); 435 return type; 436 } 437 438 // static 439 const Clipboard::FormatType& Clipboard::GetPepperCustomDataFormatType() { 440 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPepperCustomDataPboardType)); 441 return type; 442 } 443 444 } // namespace ui 445