• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
27namespace ui {
28
29namespace {
30
31// Would be nice if this were in UTCoreTypes.h, but it isn't
32NSString* 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.
36NSString* const kWebSmartPastePboardType = @"NeXT smart paste pasteboard type";
37
38// Pepper custom data format type.
39NSString* const kPepperCustomDataPboardType =
40    @"org.chromium.pepper-custom-data";
41
42NSPasteboard* 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
53Clipboard::FormatType::FormatType() : data_(nil) {
54}
55
56Clipboard::FormatType::FormatType(NSString* native_format)
57    : data_([native_format retain]) {
58}
59
60Clipboard::FormatType::FormatType(const FormatType& other)
61    : data_([other.data_ retain]) {
62}
63
64Clipboard::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
73bool Clipboard::FormatType::Equals(const FormatType& other) const {
74  return [data_ isEqualToString:other.data_];
75}
76
77Clipboard::FormatType::~FormatType() {
78  [data_ release];
79}
80
81std::string Clipboard::FormatType::Serialize() const {
82  return base::SysNSStringToUTF8(data_);
83}
84
85// static
86Clipboard::FormatType Clipboard::FormatType::Deserialize(
87    const std::string& serialization) {
88  return FormatType(base::SysUTF8ToNSString(serialization));
89}
90
91Clipboard::Clipboard() {
92  DCHECK(CalledOnValidThread());
93}
94
95Clipboard::~Clipboard() {
96  DCHECK(CalledOnValidThread());
97}
98
99void 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
112void 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
120void 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
135void Clipboard::WriteRTF(const char* rtf_data, size_t data_len) {
136  WriteData(GetRtfFormatType(), rtf_data, data_len);
137}
138
139void 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
164void 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
178void 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.
189void 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
196uint64 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
204bool 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
221void 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
229void 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
252void 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
261void 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
273void 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
304void 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
311SkBitmap 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
329void 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
343void 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
361void 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
370Clipboard::FormatType Clipboard::GetFormatType(
371    const std::string& format_string) {
372  return FormatType::Deserialize(format_string);
373}
374
375// static
376const Clipboard::FormatType& Clipboard::GetUrlFormatType() {
377  CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSURLPboardType));
378  return type;
379}
380
381// static
382const Clipboard::FormatType& Clipboard::GetUrlWFormatType() {
383  return GetUrlFormatType();
384}
385
386// static
387const Clipboard::FormatType& Clipboard::GetPlainTextFormatType() {
388  CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSStringPboardType));
389  return type;
390}
391
392// static
393const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType() {
394  return GetPlainTextFormatType();
395}
396
397// static
398const Clipboard::FormatType& Clipboard::GetFilenameFormatType() {
399  CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSFilenamesPboardType));
400  return type;
401}
402
403// static
404const Clipboard::FormatType& Clipboard::GetFilenameWFormatType() {
405  return GetFilenameFormatType();
406}
407
408// static
409const Clipboard::FormatType& Clipboard::GetHtmlFormatType() {
410  CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSHTMLPboardType));
411  return type;
412}
413
414// static
415const Clipboard::FormatType& Clipboard::GetRtfFormatType() {
416  CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSRTFPboardType));
417  return type;
418}
419
420// static
421const Clipboard::FormatType& Clipboard::GetBitmapFormatType() {
422  CR_DEFINE_STATIC_LOCAL(FormatType, type, (NSTIFFPboardType));
423  return type;
424}
425
426// static
427const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType() {
428  CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebSmartPastePboardType));
429  return type;
430}
431
432// static
433const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() {
434  CR_DEFINE_STATIC_LOCAL(FormatType, type, (kWebCustomDataPboardType));
435  return type;
436}
437
438// static
439const Clipboard::FormatType& Clipboard::GetPepperCustomDataFormatType() {
440  CR_DEFINE_STATIC_LOCAL(FormatType, type, (kPepperCustomDataPboardType));
441  return type;
442}
443
444}  // namespace ui
445