Home | History | Annotate | Download | only in clipboard
      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