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.
5 #include "ui/base/clipboard/clipboard_mac.h"
7 #import <Cocoa/Cocoa.h>
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/geometry/size.h"
25 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
31 // Would be nice if this were in UTCoreTypes.h, but it isn't
32 NSString* const kUTTypeURLName = @"public.url-name";
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";
38 // Pepper custom data format type.
39 NSString* const kPepperCustomDataPboardType =
40 @"org.chromium.pepper-custom-data";
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
46 NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
53 // Clipboard::FormatType implementation.
54 Clipboard::FormatType::FormatType() : data_(nil) {
57 Clipboard::FormatType::FormatType(NSString* native_format)
58 : data_([native_format retain]) {
61 Clipboard::FormatType::FormatType(const FormatType& other)
62 : data_([other.data_ retain]) {
65 Clipboard::FormatType& Clipboard::FormatType::operator=(
66 const FormatType& other) {
69 data_ = [other.data_ retain];
74 bool Clipboard::FormatType::Equals(const FormatType& other) const {
75 return [data_ isEqualToString:other.data_];
78 Clipboard::FormatType::~FormatType() {
82 std::string Clipboard::FormatType::Serialize() const {
83 return base::SysNSStringToUTF8(data_);
87 Clipboard::FormatType Clipboard::FormatType::Deserialize(
88 const std::string& serialization) {
89 return FormatType(base::SysUTF8ToNSString(serialization));
92 bool Clipboard::FormatType::operator<(const FormatType& other) const {
93 return [data_ compare:other.data_] == NSOrderedAscending;
96 // Various predefined FormatTypes.
98 Clipboard::FormatType Clipboard::GetFormatType(
99 const std::string& format_string) {
100 return FormatType::Deserialize(format_string);
104 const Clipboard::FormatType& Clipboard::GetUrlFormatType() {
105 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSURLPboardType));
110 const Clipboard::FormatType& Clipboard::GetUrlWFormatType() {
111 return GetUrlFormatType();
115 const Clipboard::FormatType& Clipboard::GetPlainTextFormatType() {
116 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSStringPboardType));
121 const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType() {
122 return GetPlainTextFormatType();
126 const Clipboard::FormatType& Clipboard::GetFilenameFormatType() {
127 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSFilenamesPboardType));
132 const Clipboard::FormatType& Clipboard::GetFilenameWFormatType() {
133 return GetFilenameFormatType();
137 const Clipboard::FormatType& Clipboard::GetHtmlFormatType() {
138 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSHTMLPboardType));
143 const Clipboard::FormatType& Clipboard::GetRtfFormatType() {
144 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSRTFPboardType));
149 const Clipboard::FormatType& Clipboard::GetBitmapFormatType() {
150 CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSTIFFPboardType));
155 const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType() {
156 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebSmartPastePboardType));
161 const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() {
162 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebCustomDataPboardType));
167 const Clipboard::FormatType& Clipboard::GetPepperCustomDataFormatType() {
168 CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPepperCustomDataPboardType));
172 // Clipboard factory method.
174 Clipboard* Clipboard::Create() {
175 return new ClipboardMac;
178 // ClipboardMac implementation.
179 ClipboardMac::ClipboardMac() {
180 DCHECK(CalledOnValidThread());
183 ClipboardMac::~ClipboardMac() {
184 DCHECK(CalledOnValidThread());
187 uint64 ClipboardMac::GetSequenceNumber(ClipboardType type) const {
188 DCHECK(CalledOnValidThread());
189 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE);
191 NSPasteboard* pb = GetPasteboard();
192 return [pb changeCount];
195 bool ClipboardMac::IsFormatAvailable(const FormatType& format,
196 ClipboardType type) const {
197 DCHECK(CalledOnValidThread());
198 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE);
200 NSPasteboard* pb = GetPasteboard();
201 NSArray* types = [pb types];
203 // Safari only places RTF on the pasteboard, never HTML. We can convert RTF
204 // to HTML, so the presence of either indicates success when looking for HTML.
205 if ([format.ToNSString() isEqualToString:NSHTMLPboardType]) {
206 return [types containsObject:NSHTMLPboardType] ||
207 [types containsObject:NSRTFPboardType];
209 return [types containsObject:format.ToNSString()];
212 void ClipboardMac::Clear(ClipboardType type) {
213 DCHECK(CalledOnValidThread());
214 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE);
216 NSPasteboard* pb = GetPasteboard();
217 [pb declareTypes:[NSArray array] owner:nil];
220 void ClipboardMac::ReadAvailableTypes(ClipboardType type,
221 std::vector<base::string16>* types,
222 bool* contains_filenames) const {
223 DCHECK(CalledOnValidThread());
225 if (IsFormatAvailable(Clipboard::GetPlainTextFormatType(), type))
226 types->push_back(base::UTF8ToUTF16(kMimeTypeText));
227 if (IsFormatAvailable(Clipboard::GetHtmlFormatType(), type))
228 types->push_back(base::UTF8ToUTF16(kMimeTypeHTML));
229 if (IsFormatAvailable(Clipboard::GetRtfFormatType(), type))
230 types->push_back(base::UTF8ToUTF16(kMimeTypeRTF));
231 if ([NSImage canInitWithPasteboard:GetPasteboard()])
232 types->push_back(base::UTF8ToUTF16(kMimeTypePNG));
233 *contains_filenames = false;
235 NSPasteboard* pb = GetPasteboard();
236 if ([[pb types] containsObject:kWebCustomDataPboardType]) {
237 NSData* data = [pb dataForType:kWebCustomDataPboardType];
239 ReadCustomDataTypes([data bytes], [data length], types);
243 void ClipboardMac::ReadText(ClipboardType type, base::string16* result) const {
244 DCHECK(CalledOnValidThread());
245 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE);
246 NSPasteboard* pb = GetPasteboard();
247 NSString* contents = [pb stringForType:NSStringPboardType];
249 *result = base::SysNSStringToUTF16(contents);
252 void ClipboardMac::ReadAsciiText(ClipboardType type,
253 std::string* result) const {
254 DCHECK(CalledOnValidThread());
255 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE);
256 NSPasteboard* pb = GetPasteboard();
257 NSString* contents = [pb stringForType:NSStringPboardType];
262 result->assign([contents UTF8String]);
265 void ClipboardMac::ReadHTML(ClipboardType type,
266 base::string16* markup,
267 std::string* src_url,
268 uint32* fragment_start,
269 uint32* fragment_end) const {
270 DCHECK(CalledOnValidThread());
271 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE);
273 // TODO(avi): src_url?
278 NSPasteboard* pb = GetPasteboard();
279 NSArray* supportedTypes = [NSArray arrayWithObjects:NSHTMLPboardType,
283 NSString* bestType = [pb availableTypeFromArray:supportedTypes];
285 NSString* contents = [pb stringForType:bestType];
286 if ([bestType isEqualToString:NSRTFPboardType])
287 contents = [pb htmlFromRtf];
288 *markup = base::SysNSStringToUTF16(contents);
292 DCHECK(markup->length() <= kuint32max);
293 *fragment_end = static_cast<uint32>(markup->length());
296 void ClipboardMac::ReadRTF(ClipboardType type, std::string* result) const {
297 DCHECK(CalledOnValidThread());
298 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE);
300 return ReadData(GetRtfFormatType(), result);
303 SkBitmap ClipboardMac::ReadImage(ClipboardType type) const {
304 DCHECK(CalledOnValidThread());
305 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE);
307 // If the pasteboard's image data is not to its liking, the guts of NSImage
308 // may throw, and that exception will leak. Prevent a crash in that case;
309 // a blank image is better.
310 base::scoped_nsobject<NSImage> image(base::mac::RunBlockIgnoringExceptions(^{
311 return [[NSImage alloc] initWithPasteboard:GetPasteboard()];
315 bitmap = gfx::NSImageToSkBitmapWithColorSpace(
316 image.get(), /*is_opaque=*/ false, base::mac::GetSystemColorSpace());
321 void ClipboardMac::ReadCustomData(ClipboardType clipboard_type,
322 const base::string16& type,
323 base::string16* result) const {
324 DCHECK(CalledOnValidThread());
325 DCHECK_EQ(clipboard_type, CLIPBOARD_TYPE_COPY_PASTE);
327 NSPasteboard* pb = GetPasteboard();
328 if ([[pb types] containsObject:kWebCustomDataPboardType]) {
329 NSData* data = [pb dataForType:kWebCustomDataPboardType];
331 ReadCustomDataForType([data bytes], [data length], type, result);
335 void ClipboardMac::ReadBookmark(base::string16* title, std::string* url) const {
336 DCHECK(CalledOnValidThread());
337 NSPasteboard* pb = GetPasteboard();
340 NSString* contents = [pb stringForType:kUTTypeURLName];
341 *title = base::SysNSStringToUTF16(contents);
345 NSString* url_string = [[NSURL URLFromPasteboard:pb] absoluteString];
349 url->assign([url_string UTF8String]);
353 void ClipboardMac::ReadData(const FormatType& format,
354 std::string* result) const {
355 DCHECK(CalledOnValidThread());
356 NSPasteboard* pb = GetPasteboard();
357 NSData* data = [pb dataForType:format.ToNSString()];
359 result->assign(static_cast<const char*>([data bytes]), [data length]);
362 void ClipboardMac::WriteObjects(ClipboardType type, const ObjectMap& objects) {
363 DCHECK(CalledOnValidThread());
364 DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE);
366 NSPasteboard* pb = GetPasteboard();
367 [pb declareTypes:[NSArray array] owner:nil];
369 for (ObjectMap::const_iterator iter = objects.begin(); iter != objects.end();
371 DispatchObject(static_cast<ObjectType>(iter->first), iter->second);
375 void ClipboardMac::WriteText(const char* text_data, size_t text_len) {
376 std::string text_str(text_data, text_len);
377 NSString* text = base::SysUTF8ToNSString(text_str);
378 NSPasteboard* pb = GetPasteboard();
379 [pb addTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
380 [pb setString:text forType:NSStringPboardType];
383 void ClipboardMac::WriteHTML(const char* markup_data,
385 const char* url_data,
387 // We need to mark it as utf-8. (see crbug.com/11957)
388 std::string html_fragment_str("<meta charset='utf-8'>");
389 html_fragment_str.append(markup_data, markup_len);
390 NSString* html_fragment = base::SysUTF8ToNSString(html_fragment_str);
392 // TODO(avi): url_data?
393 NSPasteboard* pb = GetPasteboard();
394 [pb addTypes:[NSArray arrayWithObject:NSHTMLPboardType] owner:nil];
395 [pb setString:html_fragment forType:NSHTMLPboardType];
398 void ClipboardMac::WriteRTF(const char* rtf_data, size_t data_len) {
399 WriteData(GetRtfFormatType(), rtf_data, data_len);
402 void ClipboardMac::WriteBookmark(const char* title_data,
404 const char* url_data,
406 std::string title_str(title_data, title_len);
407 NSString* title = base::SysUTF8ToNSString(title_str);
408 std::string url_str(url_data, url_len);
409 NSString* url = base::SysUTF8ToNSString(url_str);
411 // TODO(playmobil): In the Windows version of this function, an HTML
412 // representation of the bookmark is also added to the clipboard, to support
413 // drag and drop of web shortcuts. I don't think we need to do this on the
414 // Mac, but we should double check later on.
415 NSURL* nsurl = [NSURL URLWithString:url];
417 NSPasteboard* pb = GetPasteboard();
418 // passing UTIs into the pasteboard methods is valid >= 10.5
419 [pb addTypes:[NSArray arrayWithObjects:NSURLPboardType, kUTTypeURLName, nil]
421 [nsurl writeToPasteboard:pb];
422 [pb setString:title forType:kUTTypeURLName];
425 void ClipboardMac::WriteBitmap(const SkBitmap& bitmap) {
426 NSImage* image = gfx::SkBitmapToNSImageWithColorSpace(
427 bitmap, base::mac::GetSystemColorSpace());
428 // An API to ask the NSImage to write itself to the clipboard comes in 10.6 :(
429 // For now, spit out the image as a TIFF.
430 NSPasteboard* pb = GetPasteboard();
431 [pb addTypes:[NSArray arrayWithObject:NSTIFFPboardType] owner:nil];
432 NSData* tiff_data = [image TIFFRepresentation];
433 LOG_IF(ERROR, tiff_data == NULL) << "Failed to allocate image for clipboard";
435 [pb setData:tiff_data forType:NSTIFFPboardType];
439 void ClipboardMac::WriteData(const FormatType& format,
440 const char* data_data,
442 NSPasteboard* pb = GetPasteboard();
443 [pb addTypes:[NSArray arrayWithObject:format.ToNSString()] owner:nil];
444 [pb setData:[NSData dataWithBytes:data_data length:data_len]
445 forType:format.ToNSString()];
448 // Write an extra flavor that signifies WebKit was the last to modify the
449 // pasteboard. This flavor has no data.
450 void ClipboardMac::WriteWebSmartPaste() {
451 NSPasteboard* pb = GetPasteboard();
452 NSString* format = GetWebKitSmartPasteFormatType().ToNSString();
453 [pb addTypes:[NSArray arrayWithObject:format] owner:nil];
454 [pb setData:nil forType:format];