| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/base/clipboard/clipboard.h" |
| |
| #import <Cocoa/Cocoa.h> |
| |
| #include "base/basictypes.h" |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/mac/mac_util.h" |
| #include "base/mac/scoped_cftyperef.h" |
| #import "base/mac/scoped_nsexception_enabler.h" |
| #include "base/mac/scoped_nsobject.h" |
| #include "base/stl_util.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "skia/ext/skia_utils_mac.h" |
| #import "third_party/mozilla/NSPasteboard+Utils.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/base/clipboard/custom_data_helper.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" |
| #include "ui/gfx/size.h" |
| |
| namespace ui { |
| |
| namespace { |
| |
| // Would be nice if this were in UTCoreTypes.h, but it isn't |
| NSString* const kUTTypeURLName = @"public.url-name"; |
| |
| // Tells us if WebKit was the last to write to the pasteboard. There's no |
| // actual data associated with this type. |
| NSString* const kWebSmartPastePboardType = @"NeXT smart paste pasteboard type"; |
| |
| // Pepper custom data format type. |
| NSString* const kPepperCustomDataPboardType = |
| @"org.chromium.pepper-custom-data"; |
| |
| NSPasteboard* GetPasteboard() { |
| // The pasteboard should not be nil in a UI session, but this handy DCHECK |
| // can help track down problems if someone tries using clipboard code outside |
| // of a UI session. |
| NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; |
| DCHECK(pasteboard); |
| return pasteboard; |
| } |
| |
| } // namespace |
| |
| Clipboard::FormatType::FormatType() : data_(nil) { |
| } |
| |
| Clipboard::FormatType::FormatType(NSString* native_format) |
| : data_([native_format retain]) { |
| } |
| |
| Clipboard::FormatType::FormatType(const FormatType& other) |
| : data_([other.data_ retain]) { |
| } |
| |
| Clipboard::FormatType& Clipboard::FormatType::operator=( |
| const FormatType& other) { |
| if (this != &other) { |
| [data_ release]; |
| data_ = [other.data_ retain]; |
| } |
| return *this; |
| } |
| |
| Clipboard::FormatType::~FormatType() { |
| [data_ release]; |
| } |
| |
| std::string Clipboard::FormatType::Serialize() const { |
| return base::SysNSStringToUTF8(data_); |
| } |
| |
| // static |
| Clipboard::FormatType Clipboard::FormatType::Deserialize( |
| const std::string& serialization) { |
| return FormatType(base::SysUTF8ToNSString(serialization)); |
| } |
| |
| Clipboard::Clipboard() { |
| DCHECK(CalledOnValidThread()); |
| } |
| |
| Clipboard::~Clipboard() { |
| DCHECK(CalledOnValidThread()); |
| } |
| |
| void Clipboard::WriteObjects(ClipboardType type, const ObjectMap& objects) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); |
| |
| NSPasteboard* pb = GetPasteboard(); |
| [pb declareTypes:[NSArray array] owner:nil]; |
| |
| for (ObjectMap::const_iterator iter = objects.begin(); |
| iter != objects.end(); ++iter) { |
| DispatchObject(static_cast<ObjectType>(iter->first), iter->second); |
| } |
| } |
| |
| void Clipboard::WriteText(const char* text_data, size_t text_len) { |
| std::string text_str(text_data, text_len); |
| NSString *text = base::SysUTF8ToNSString(text_str); |
| NSPasteboard* pb = GetPasteboard(); |
| [pb addTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; |
| [pb setString:text forType:NSStringPboardType]; |
| } |
| |
| void Clipboard::WriteHTML(const char* markup_data, |
| size_t markup_len, |
| const char* url_data, |
| size_t url_len) { |
| // We need to mark it as utf-8. (see crbug.com/11957) |
| std::string html_fragment_str("<meta charset='utf-8'>"); |
| html_fragment_str.append(markup_data, markup_len); |
| NSString *html_fragment = base::SysUTF8ToNSString(html_fragment_str); |
| |
| // TODO(avi): url_data? |
| NSPasteboard* pb = GetPasteboard(); |
| [pb addTypes:[NSArray arrayWithObject:NSHTMLPboardType] owner:nil]; |
| [pb setString:html_fragment forType:NSHTMLPboardType]; |
| } |
| |
| void Clipboard::WriteRTF(const char* rtf_data, size_t data_len) { |
| WriteData(GetRtfFormatType(), rtf_data, data_len); |
| } |
| |
| void Clipboard::WriteBookmark(const char* title_data, |
| size_t title_len, |
| const char* url_data, |
| size_t url_len) { |
| std::string title_str(title_data, title_len); |
| NSString *title = base::SysUTF8ToNSString(title_str); |
| std::string url_str(url_data, url_len); |
| NSString *url = base::SysUTF8ToNSString(url_str); |
| |
| // TODO(playmobil): In the Windows version of this function, an HTML |
| // representation of the bookmark is also added to the clipboard, to support |
| // drag and drop of web shortcuts. I don't think we need to do this on the |
| // Mac, but we should double check later on. |
| NSURL* nsurl = [NSURL URLWithString:url]; |
| |
| NSPasteboard* pb = GetPasteboard(); |
| // passing UTIs into the pasteboard methods is valid >= 10.5 |
| [pb addTypes:[NSArray arrayWithObjects:NSURLPboardType, |
| kUTTypeURLName, |
| nil] |
| owner:nil]; |
| [nsurl writeToPasteboard:pb]; |
| [pb setString:title forType:kUTTypeURLName]; |
| } |
| |
| void Clipboard::WriteBitmap(const SkBitmap& bitmap) { |
| NSImage* image = gfx::SkBitmapToNSImageWithColorSpace( |
| bitmap, base::mac::GetSystemColorSpace()); |
| // An API to ask the NSImage to write itself to the clipboard comes in 10.6 :( |
| // For now, spit out the image as a TIFF. |
| NSPasteboard* pb = GetPasteboard(); |
| [pb addTypes:[NSArray arrayWithObject:NSTIFFPboardType] owner:nil]; |
| NSData *tiff_data = [image TIFFRepresentation]; |
| LOG_IF(ERROR, tiff_data == NULL) << "Failed to allocate image for clipboard"; |
| if (tiff_data) { |
| [pb setData:tiff_data forType:NSTIFFPboardType]; |
| } |
| } |
| |
| void Clipboard::WriteData(const FormatType& format, |
| const char* data_data, |
| size_t data_len) { |
| NSPasteboard* pb = GetPasteboard(); |
| [pb addTypes:[NSArray arrayWithObject:format.ToNSString()] owner:nil]; |
| [pb setData:[NSData dataWithBytes:data_data length:data_len] |
| forType:format.ToNSString()]; |
| } |
| |
| // Write an extra flavor that signifies WebKit was the last to modify the |
| // pasteboard. This flavor has no data. |
| void Clipboard::WriteWebSmartPaste() { |
| NSPasteboard* pb = GetPasteboard(); |
| NSString* format = GetWebKitSmartPasteFormatType().ToNSString(); |
| [pb addTypes:[NSArray arrayWithObject:format] owner:nil]; |
| [pb setData:nil forType:format]; |
| } |
| |
| uint64 Clipboard::GetSequenceNumber(ClipboardType type) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); |
| |
| NSPasteboard* pb = GetPasteboard(); |
| return [pb changeCount]; |
| } |
| |
| bool Clipboard::IsFormatAvailable(const FormatType& format, |
| ClipboardType type) const { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); |
| |
| NSPasteboard* pb = GetPasteboard(); |
| NSArray* types = [pb types]; |
| |
| // Safari only places RTF on the pasteboard, never HTML. We can convert RTF |
| // to HTML, so the presence of either indicates success when looking for HTML. |
| if ([format.ToNSString() isEqualToString:NSHTMLPboardType]) { |
| return [types containsObject:NSHTMLPboardType] || |
| [types containsObject:NSRTFPboardType]; |
| } |
| return [types containsObject:format.ToNSString()]; |
| } |
| |
| void Clipboard::Clear(ClipboardType type) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); |
| |
| NSPasteboard* pb = GetPasteboard(); |
| [pb declareTypes:[NSArray array] owner:nil]; |
| } |
| |
| void Clipboard::ReadAvailableTypes(ClipboardType type, |
| std::vector<string16>* types, |
| bool* contains_filenames) const { |
| DCHECK(CalledOnValidThread()); |
| types->clear(); |
| if (IsFormatAvailable(Clipboard::GetPlainTextFormatType(), type)) |
| types->push_back(UTF8ToUTF16(kMimeTypeText)); |
| if (IsFormatAvailable(Clipboard::GetHtmlFormatType(), type)) |
| types->push_back(UTF8ToUTF16(kMimeTypeHTML)); |
| if (IsFormatAvailable(Clipboard::GetRtfFormatType(), type)) |
| types->push_back(UTF8ToUTF16(kMimeTypeRTF)); |
| if ([NSImage canInitWithPasteboard:GetPasteboard()]) |
| types->push_back(UTF8ToUTF16(kMimeTypePNG)); |
| *contains_filenames = false; |
| |
| NSPasteboard* pb = GetPasteboard(); |
| if ([[pb types] containsObject:kWebCustomDataPboardType]) { |
| NSData* data = [pb dataForType:kWebCustomDataPboardType]; |
| if ([data length]) |
| ReadCustomDataTypes([data bytes], [data length], types); |
| } |
| } |
| |
| void Clipboard::ReadText(ClipboardType type, string16* result) const { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); |
| NSPasteboard* pb = GetPasteboard(); |
| NSString* contents = [pb stringForType:NSStringPboardType]; |
| |
| UTF8ToUTF16([contents UTF8String], |
| [contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding], |
| result); |
| } |
| |
| void Clipboard::ReadAsciiText(ClipboardType type, std::string* result) const { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); |
| NSPasteboard* pb = GetPasteboard(); |
| NSString* contents = [pb stringForType:NSStringPboardType]; |
| |
| if (!contents) |
| result->clear(); |
| else |
| result->assign([contents UTF8String]); |
| } |
| |
| void Clipboard::ReadHTML(ClipboardType type, |
| string16* markup, |
| std::string* src_url, |
| uint32* fragment_start, |
| uint32* fragment_end) const { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); |
| |
| // TODO(avi): src_url? |
| markup->clear(); |
| if (src_url) |
| src_url->clear(); |
| |
| NSPasteboard* pb = GetPasteboard(); |
| NSArray* supportedTypes = [NSArray arrayWithObjects:NSHTMLPboardType, |
| NSRTFPboardType, |
| NSStringPboardType, |
| nil]; |
| NSString* bestType = [pb availableTypeFromArray:supportedTypes]; |
| if (bestType) { |
| NSString* contents = [pb stringForType:bestType]; |
| if ([bestType isEqualToString:NSRTFPboardType]) |
| contents = [pb htmlFromRtf]; |
| UTF8ToUTF16([contents UTF8String], |
| [contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding], |
| markup); |
| } |
| |
| *fragment_start = 0; |
| DCHECK(markup->length() <= kuint32max); |
| *fragment_end = static_cast<uint32>(markup->length()); |
| } |
| |
| void Clipboard::ReadRTF(ClipboardType type, std::string* result) const { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); |
| |
| return ReadData(GetRtfFormatType(), result); |
| } |
| |
| SkBitmap Clipboard::ReadImage(ClipboardType type) const { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); |
| |
| // If the pasteboard's image data is not to its liking, the guts of NSImage |
| // may throw, and that exception will leak. Prevent a crash in that case; |
| // a blank image is better. |
| base::scoped_nsobject<NSImage> image(base::mac::RunBlockIgnoringExceptions(^{ |
| return [[NSImage alloc] initWithPasteboard:GetPasteboard()]; |
| })); |
| SkBitmap bitmap; |
| if (image.get()) { |
| bitmap = gfx::NSImageToSkBitmapWithColorSpace( |
| image.get(), /*is_opaque=*/ false, base::mac::GetSystemColorSpace()); |
| } |
| return bitmap; |
| } |
| |
| void Clipboard::ReadCustomData(ClipboardType clipboard_type, |
| const string16& type, |
| string16* result) const { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(clipboard_type, CLIPBOARD_TYPE_COPY_PASTE); |
| |
| NSPasteboard* pb = GetPasteboard(); |
| if ([[pb types] containsObject:kWebCustomDataPboardType]) { |
| NSData* data = [pb dataForType:kWebCustomDataPboardType]; |
| if ([data length]) |
| ReadCustomDataForType([data bytes], [data length], type, result); |
| } |
| } |
| |
| void Clipboard::ReadBookmark(string16* title, std::string* url) const { |
| DCHECK(CalledOnValidThread()); |
| NSPasteboard* pb = GetPasteboard(); |
| |
| if (title) { |
| NSString* contents = [pb stringForType:kUTTypeURLName]; |
| UTF8ToUTF16([contents UTF8String], |
| [contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding], |
| title); |
| } |
| |
| if (url) { |
| NSString* url_string = [[NSURL URLFromPasteboard:pb] absoluteString]; |
| if (!url_string) |
| url->clear(); |
| else |
| url->assign([url_string UTF8String]); |
| } |
| } |
| |
| void Clipboard::ReadData(const FormatType& format, std::string* result) const { |
| DCHECK(CalledOnValidThread()); |
| NSPasteboard* pb = GetPasteboard(); |
| NSData* data = [pb dataForType:format.ToNSString()]; |
| if ([data length]) |
| result->assign(static_cast<const char*>([data bytes]), [data length]); |
| } |
| |
| // static |
| Clipboard::FormatType Clipboard::GetFormatType( |
| const std::string& format_string) { |
| return FormatType::Deserialize(format_string); |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetUrlFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSURLPboardType)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetUrlWFormatType() { |
| return GetUrlFormatType(); |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetPlainTextFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSStringPboardType)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType() { |
| return GetPlainTextFormatType(); |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetFilenameFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSFilenamesPboardType)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetFilenameWFormatType() { |
| return GetFilenameFormatType(); |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetHtmlFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSHTMLPboardType)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetRtfFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSRTFPboardType)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetBitmapFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSTIFFPboardType)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebSmartPastePboardType)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebCustomDataPboardType)); |
| return type; |
| } |
| |
| // static |
| const Clipboard::FormatType& Clipboard::GetPepperCustomDataFormatType() { |
| CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPepperCustomDataPboardType)); |
| return type; |
| } |
| |
| } // namespace ui |