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