1/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2/* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6#include "mozilla/Logging.h"
7
8#include "mozilla/Unused.h"
9
10#include "gfxPlatform.h"
11#include "nsArrayUtils.h"
12#include "nsCOMPtr.h"
13#include "nsClipboard.h"
14#include "nsString.h"
15#include "nsISupportsPrimitives.h"
16#include "nsPrimitiveHelpers.h"
17#include "nsMemory.h"
18#include "nsIFile.h"
19#include "nsStringStream.h"
20#include "nsDragService.h"
21#include "nsEscape.h"
22#include "nsPrintfCString.h"
23#include "nsObjCExceptions.h"
24#include "imgIContainer.h"
25#include "nsCocoaUtils.h"
26
27using mozilla::gfx::DataSourceSurface;
28using mozilla::gfx::SourceSurface;
29using mozilla::LogLevel;
30
31extern mozilla::LazyLogModule sCocoaLog;
32
33extern void EnsureLogInitialized();
34
35mozilla::StaticRefPtr<nsITransferable> nsClipboard::sSelectionCache;
36
37@implementation UTIHelper
38
39+ (NSString*)stringFromPboardType:(NSString*)aType
40{
41  if ([aType isEqualToString:kMozWildcardPboardType] ||
42      [aType isEqualToString:kMozCustomTypesPboardType] ||
43      [aType isEqualToString:kPublicUrlPboardType] ||
44      [aType isEqualToString:kPublicUrlNamePboardType] ||
45      [aType isEqualToString:kMozFileUrlsPboardType] ||
46      [aType isEqualToString:(NSString*)kPasteboardTypeFileURLPromise] ||
47      [aType isEqualToString:(NSString*)kPasteboardTypeFilePromiseContent] ||
48      [aType isEqualToString:(NSString*)kUTTypeFileURL] ||
49      [aType isEqualToString:NSStringPboardType] ||
50      [aType isEqualToString:NSPasteboardTypeString] ||
51      [aType isEqualToString:NSPasteboardTypeHTML] ||
52      [aType isEqualToString:NSPasteboardTypeRTF] ||
53      [aType isEqualToString:NSPasteboardTypeTIFF] ||
54      [aType isEqualToString:NSPasteboardTypePNG]) {
55    return [NSString stringWithString:aType];
56  }
57  NSString* dynamicType =
58    (NSString*)UTTypeCreatePreferredIdentifierForTag(kUTTagClassNSPboardType,
59                                                     (CFStringRef)aType,
60                                                     kUTTypeData);
61  NSString* result = [NSString stringWithString:dynamicType];
62  [dynamicType release];
63  return result;
64}
65
66@end // UTIHelper
67
68nsClipboard::nsClipboard()
69  : mCachedClipboard(-1)
70  , mChangeCount(0)
71  , mIgnoreEmptyNotification(false)
72{
73  EnsureLogInitialized();
74}
75
76nsClipboard::~nsClipboard()
77{
78  EmptyClipboard(kGlobalClipboard);
79  EmptyClipboard(kFindClipboard);
80  ClearSelectionCache();
81}
82
83NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
84
85// We separate this into its own function because after an @try, all local
86// variables within that function get marked as volatile, and our C++ type
87// system doesn't like volatile things.
88static NSData*
89GetDataFromPasteboard(NSPasteboard* aPasteboard, NSString* aType)
90{
91  NSData *data = nil;
92  @try {
93    data = [aPasteboard dataForType:aType];
94  } @catch (NSException* e) {
95    NS_WARNING(nsPrintfCString("Exception raised while getting data from the pasteboard: \"%s - %s\"",
96                               [[e name] UTF8String], [[e reason] UTF8String]).get());
97    mozilla::Unused << e;
98  }
99  return data;
100}
101
102void
103nsClipboard::SetSelectionCache(nsITransferable *aTransferable)
104{
105  sSelectionCache = aTransferable;
106}
107
108void
109nsClipboard::ClearSelectionCache()
110{
111  sSelectionCache = nullptr;
112}
113
114NS_IMETHODIMP
115nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard)
116{
117  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
118
119  if ((aWhichClipboard != kGlobalClipboard && aWhichClipboard != kFindClipboard) || !mTransferable)
120    return NS_ERROR_FAILURE;
121
122  mIgnoreEmptyNotification = true;
123
124  NSDictionary* pasteboardOutputDict = PasteboardDictFromTransferable(mTransferable);
125  if (!pasteboardOutputDict)
126    return NS_ERROR_FAILURE;
127
128  unsigned int outputCount = [pasteboardOutputDict count];
129  NSArray* outputKeys = [pasteboardOutputDict allKeys];
130  NSPasteboard* cocoaPasteboard;
131  if (aWhichClipboard == kFindClipboard) {
132    cocoaPasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
133    NSString* stringType =
134      [UTIHelper stringFromPboardType:NSPasteboardTypeString];
135    [cocoaPasteboard declareTypes:
136      [NSArray arrayWithObject:stringType] owner:nil];
137  } else {
138    // Write everything else out to the general pasteboard.
139    cocoaPasteboard = [NSPasteboard generalPasteboard];
140    [cocoaPasteboard declareTypes:outputKeys owner:nil];
141  }
142
143  for (unsigned int i = 0; i < outputCount; i++) {
144    NSString* currentKey = [outputKeys objectAtIndex:i];
145    id currentValue = [pasteboardOutputDict valueForKey:currentKey];
146    if (aWhichClipboard == kFindClipboard) {
147      if ([currentKey isEqualToString:
148            [UTIHelper stringFromPboardType:NSPasteboardTypeString]]) {
149        [cocoaPasteboard setString:currentValue forType:currentKey];
150      }
151    } else {
152      if ([currentKey isEqualToString:
153            [UTIHelper stringFromPboardType:NSPasteboardTypeString]] ||
154          [currentKey isEqualToString:
155            [UTIHelper stringFromPboardType:kPublicUrlPboardType]] ||
156          [currentKey isEqualToString:
157            [UTIHelper stringFromPboardType:kPublicUrlNamePboardType]]) {
158        [cocoaPasteboard setString:currentValue forType:currentKey];
159      } else if ([currentKey isEqualToString:
160                   [UTIHelper stringFromPboardType:
161                     kUrlsWithTitlesPboardType]]) {
162        [cocoaPasteboard setPropertyList:
163          [pasteboardOutputDict valueForKey:currentKey]
164                                 forType:currentKey];
165      } else if ([currentKey isEqualToString:
166                   [UTIHelper stringFromPboardType:NSPasteboardTypeHTML]]) {
167        [cocoaPasteboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
168                         forType:currentKey];
169      } else if ([currentKey isEqualToString:
170                   [UTIHelper stringFromPboardType:kMozFileUrlsPboardType]]) {
171        [cocoaPasteboard writeObjects:currentValue];
172      } else {
173        [cocoaPasteboard setData:currentValue forType:currentKey];
174      }
175    }
176  }
177
178  mCachedClipboard = aWhichClipboard;
179  mChangeCount = [cocoaPasteboard changeCount];
180
181  mIgnoreEmptyNotification = false;
182
183  return NS_OK;
184
185  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
186}
187
188nsresult
189nsClipboard::TransferableFromPasteboard(nsITransferable *aTransferable, NSPasteboard *cocoaPasteboard)
190{
191  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
192
193  // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
194  nsCOMPtr<nsIArray> flavorList;
195  nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
196  if (NS_FAILED(rv))
197    return NS_ERROR_FAILURE;
198
199  uint32_t flavorCount;
200  flavorList->GetLength(&flavorCount);
201
202  for (uint32_t i = 0; i < flavorCount; i++) {
203    nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
204    if (!currentFlavor)
205      continue;
206
207    nsCString flavorStr;
208    currentFlavor->ToString(getter_Copies(flavorStr)); // i has a flavr
209
210    // printf("looking for clipboard data of type %s\n", flavorStr.get());
211
212    NSString *pboardType = nil;
213    if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
214      NSString* pString = [cocoaPasteboard stringForType:pboardType];
215      if (!pString)
216        continue;
217
218      NSData* stringData;
219      if ([pboardType isEqualToString:
220            [UTIHelper stringFromPboardType:NSPasteboardTypeRTF]]) {
221        stringData = [pString dataUsingEncoding:NSASCIIStringEncoding];
222      } else {
223        stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
224      }
225      unsigned int dataLength = [stringData length];
226      void* clipboardDataPtr = malloc(dataLength);
227      if (!clipboardDataPtr) {
228        [pboardType release];
229        return NS_ERROR_OUT_OF_MEMORY;
230      }
231      [stringData getBytes:clipboardDataPtr];
232
233      // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
234      int32_t signedDataLength = dataLength;
235      nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr, &signedDataLength);
236      dataLength = signedDataLength;
237
238      // skip BOM (Byte Order Mark to distinguish little or big endian)
239      char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
240      if ((dataLength > 2) &&
241          ((clipboardDataPtrNoBOM[0] == 0xFEFF) ||
242           (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
243        dataLength -= sizeof(char16_t);
244        clipboardDataPtrNoBOM += 1;
245      }
246
247      nsCOMPtr<nsISupports> genericDataWrapper;
248      nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
249                                                 getter_AddRefs(genericDataWrapper));
250      aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper, dataLength);
251      free(clipboardDataPtr);
252      break;
253    }
254    else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
255      NSString* type =
256        [cocoaPasteboard availableTypeFromArray:
257          [NSArray arrayWithObject:
258            [UTIHelper stringFromPboardType:kMozCustomTypesPboardType]]];
259      if (!type) {
260        continue;
261      }
262
263      NSData* pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type);
264      if (!pasteboardData) {
265        continue;
266      }
267
268      unsigned int dataLength = [pasteboardData length];
269      void* clipboardDataPtr = malloc(dataLength);
270      if (!clipboardDataPtr) {
271        [pboardType release];
272        return NS_ERROR_OUT_OF_MEMORY;
273      }
274      [pasteboardData getBytes:clipboardDataPtr];
275
276      nsCOMPtr<nsISupports> genericDataWrapper;
277      nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtr, dataLength,
278                                                 getter_AddRefs(genericDataWrapper));
279
280      aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper, dataLength);
281      free(clipboardDataPtr);
282    }
283    else if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
284             flavorStr.EqualsLiteral(kJPGImageMime) ||
285             flavorStr.EqualsLiteral(kPNGImageMime) ||
286             flavorStr.EqualsLiteral(kGIFImageMime)) {
287      // Figure out if there's data on the pasteboard we can grab (sanity check)
288      NSString* type =
289        [cocoaPasteboard availableTypeFromArray:
290          [NSArray arrayWithObjects:
291            [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF],
292            [UTIHelper stringFromPboardType:NSPasteboardTypePNG],
293            nil]];
294      if (!type)
295        continue;
296
297      // Read data off the clipboard
298      NSData *pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type);
299      if (!pasteboardData)
300        continue;
301
302      // Figure out what type we're converting to
303      CFStringRef outputType = NULL;
304      if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
305          flavorStr.EqualsLiteral(kJPGImageMime))
306        outputType = CFSTR("public.jpeg");
307      else if (flavorStr.EqualsLiteral(kPNGImageMime))
308        outputType = CFSTR("public.png");
309      else if (flavorStr.EqualsLiteral(kGIFImageMime))
310        outputType = CFSTR("com.compuserve.gif");
311      else
312        continue;
313
314      // Use ImageIO to interpret the data on the clipboard and transcode.
315      // Note that ImageIO, like all CF APIs, allows NULLs to propagate freely
316      // and safely in most cases (like ObjC). A notable exception is CFRelease.
317      NSDictionary *options =
318        [NSDictionary dictionaryWithObjectsAndKeys:
319          (NSNumber*)kCFBooleanTrue,
320          kCGImageSourceShouldAllowFloat,
321          (type == [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF] ?
322                     [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF] :
323                     [UTIHelper stringFromPboardType:NSPasteboardTypePNG]),
324          kCGImageSourceTypeIdentifierHint, nil];
325
326      CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)pasteboardData,
327                                                            (CFDictionaryRef)options);
328      NSMutableData *encodedData = [NSMutableData data];
329      CGImageDestinationRef dest = CGImageDestinationCreateWithData((CFMutableDataRef)encodedData,
330                                                                    outputType,
331                                                                    1, NULL);
332      CGImageDestinationAddImageFromSource(dest, source, 0, NULL);
333      bool successfullyConverted = CGImageDestinationFinalize(dest);
334
335      if (successfullyConverted) {
336        // Put the converted data in a form Gecko can understand
337        nsCOMPtr<nsIInputStream> byteStream;
338        NS_NewByteInputStream(getter_AddRefs(byteStream), (const char*)[encodedData bytes],
339                                   [encodedData length], NS_ASSIGNMENT_COPY);
340
341        aTransferable->SetTransferData(flavorStr.get(), byteStream, sizeof(nsIInputStream*));
342      }
343
344      if (dest)
345        CFRelease(dest);
346      if (source)
347        CFRelease(source);
348
349      if (successfullyConverted)
350        break;
351      else
352        continue;
353    }
354    [pboardType release];
355  }
356
357  return NS_OK;
358
359  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
360}
361
362NS_IMETHODIMP
363nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, int32_t aWhichClipboard)
364{
365  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
366
367  if ((aWhichClipboard != kGlobalClipboard && aWhichClipboard != kFindClipboard) || !aTransferable)
368    return NS_ERROR_FAILURE;
369
370  NSPasteboard* cocoaPasteboard;
371  if (aWhichClipboard == kFindClipboard) {
372    cocoaPasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
373  } else {
374    cocoaPasteboard = [NSPasteboard generalPasteboard];
375  }
376  if (!cocoaPasteboard)
377    return NS_ERROR_FAILURE;
378
379  // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
380  nsCOMPtr<nsIArray> flavorList;
381  nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
382  if (NS_FAILED(rv))
383    return NS_ERROR_FAILURE;
384
385  uint32_t flavorCount;
386  flavorList->GetLength(&flavorCount);
387
388  // If we were the last ones to put something on the pasteboard, then just use the cached
389  // transferable. Otherwise clear it because it isn't relevant any more.
390  if (mCachedClipboard == aWhichClipboard &&
391      mChangeCount == [cocoaPasteboard changeCount]) {
392    if (mTransferable) {
393      for (uint32_t i = 0; i < flavorCount; i++) {
394        nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
395        if (!currentFlavor)
396          continue;
397
398        nsCString flavorStr;
399        currentFlavor->ToString(getter_Copies(flavorStr));
400
401        nsCOMPtr<nsISupports> dataSupports;
402        uint32_t dataSize = 0;
403        rv = mTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(dataSupports), &dataSize);
404        if (NS_SUCCEEDED(rv)) {
405          aTransferable->SetTransferData(flavorStr.get(), dataSupports, dataSize);
406          return NS_OK; // maybe try to fill in more types? Is there a point?
407        }
408      }
409    }
410  } else {
411    EmptyClipboard(aWhichClipboard);
412  }
413
414  // at this point we can't satisfy the request from cache data so let's look
415  // for things other people put on the system clipboard
416
417  return nsClipboard::TransferableFromPasteboard(aTransferable, cocoaPasteboard);
418
419  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
420}
421
422// returns true if we have *any* of the passed in flavors available for pasting
423NS_IMETHODIMP
424nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength,
425                                    int32_t aWhichClipboard, bool* outResult)
426{
427  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
428
429  *outResult = false;
430
431  if ((aWhichClipboard != kGlobalClipboard) || !aFlavorList)
432    return NS_OK;
433
434  // first see if we have data for this in our cached transferable
435  if (mTransferable) {
436    nsCOMPtr<nsIArray> transferableFlavorList;
437    nsresult rv = mTransferable->FlavorsTransferableCanImport(getter_AddRefs(transferableFlavorList));
438    if (NS_SUCCEEDED(rv)) {
439      uint32_t transferableFlavorCount;
440      transferableFlavorList->GetLength(&transferableFlavorCount);
441      for (uint32_t j = 0; j < transferableFlavorCount; j++) {
442        nsCOMPtr<nsISupportsCString> currentTransferableFlavor =
443            do_QueryElementAt(transferableFlavorList, j);
444        if (!currentTransferableFlavor)
445          continue;
446        nsCString transferableFlavorStr;
447        currentTransferableFlavor->ToString(getter_Copies(transferableFlavorStr));
448
449        for (uint32_t k = 0; k < aLength; k++) {
450          if (transferableFlavorStr.Equals(aFlavorList[k])) {
451            *outResult = true;
452            return NS_OK;
453          }
454        }
455      }
456    }
457  }
458
459  NSPasteboard* generalPBoard = [NSPasteboard generalPasteboard];
460
461  for (uint32_t i = 0; i < aLength; i++) {
462    nsDependentCString mimeType(aFlavorList[i]);
463    NSString *pboardType = nil;
464
465    if (nsClipboard::IsStringType(mimeType, &pboardType)) {
466      NSString* availableType = [generalPBoard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
467      if (availableType && [availableType isEqualToString:pboardType]) {
468        *outResult = true;
469        break;
470      }
471    } else if (!strcmp(aFlavorList[i], kCustomTypesMime)) {
472      NSString* availableType =
473        [generalPBoard availableTypeFromArray:
474          [NSArray arrayWithObject:
475            [UTIHelper stringFromPboardType:kMozCustomTypesPboardType]]];
476      if (availableType) {
477        *outResult = true;
478        break;
479      }
480    } else if (!strcmp(aFlavorList[i], kJPEGImageMime) ||
481               !strcmp(aFlavorList[i], kJPGImageMime) ||
482               !strcmp(aFlavorList[i], kPNGImageMime) ||
483               !strcmp(aFlavorList[i], kGIFImageMime)) {
484      NSString* availableType =
485        [generalPBoard availableTypeFromArray:
486          [NSArray arrayWithObjects:
487            [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF],
488            [UTIHelper stringFromPboardType:NSPasteboardTypePNG],
489            nil]];
490      if (availableType) {
491        *outResult = true;
492        break;
493      }
494    }
495    [pboardType release];
496  }
497
498  return NS_OK;
499
500  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
501}
502
503NS_IMETHODIMP
504nsClipboard::SupportsFindClipboard(bool *_retval)
505{
506  NS_ENSURE_ARG_POINTER(_retval);
507  *_retval = true;
508  return NS_OK;
509}
510
511// This function converts anything that other applications might understand into the system format
512// and puts it into a dictionary which it returns.
513// static
514NSDictionary*
515nsClipboard::PasteboardDictFromTransferable(nsITransferable* aTransferable)
516{
517  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
518
519  if (!aTransferable)
520    return nil;
521
522  NSMutableDictionary* pasteboardOutputDict = [NSMutableDictionary dictionary];
523
524  nsCOMPtr<nsIArray> flavorList;
525  nsresult rv = aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
526  if (NS_FAILED(rv))
527    return nil;
528
529  uint32_t flavorCount;
530  flavorList->GetLength(&flavorCount);
531  for (uint32_t i = 0; i < flavorCount; i++) {
532    nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
533    if (!currentFlavor)
534      continue;
535
536    nsCString flavorStr;
537    currentFlavor->ToString(getter_Copies(flavorStr));
538
539    MOZ_LOG(sCocoaLog, LogLevel::Info, ("writing out clipboard data of type %s (%d)\n", flavorStr.get(), i));
540
541    NSString *pboardType = nil;
542
543    if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
544      void* data = nullptr;
545      uint32_t dataSize = 0;
546      nsCOMPtr<nsISupports> genericDataWrapper;
547      rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericDataWrapper), &dataSize);
548      nsPrimitiveHelpers::CreateDataFromPrimitive(flavorStr, genericDataWrapper, &data, dataSize);
549
550      NSString* nativeString;
551      if (data)
552        nativeString = [NSString stringWithCharacters:(const unichar*)data length:(dataSize / sizeof(char16_t))];
553      else
554        nativeString = [NSString string];
555
556      // be nice to Carbon apps, normalize the receiver's contents using Form C.
557      nativeString = [nativeString precomposedStringWithCanonicalMapping];
558
559      [pasteboardOutputDict setObject:nativeString forKey:pboardType];
560
561      free(data);
562    }
563    else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
564      void* data = nullptr;
565      uint32_t dataSize = 0;
566      nsCOMPtr<nsISupports> genericDataWrapper;
567      rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericDataWrapper), &dataSize);
568      nsPrimitiveHelpers::CreateDataFromPrimitive(flavorStr, genericDataWrapper, &data, dataSize);
569
570      if (data) {
571        NSData* nativeData = [NSData dataWithBytes:data length:dataSize];
572        NSString* customType =
573          [UTIHelper stringFromPboardType:kMozCustomTypesPboardType];
574        [pasteboardOutputDict setObject:nativeData forKey:customType];
575        free(data);
576      }
577    }
578    else if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
579             flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime) ||
580             flavorStr.EqualsLiteral(kNativeImageMime)) {
581      uint32_t dataSize = 0;
582      nsCOMPtr<nsISupports> transferSupports;
583      aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(transferSupports), &dataSize);
584      nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive(do_QueryInterface(transferSupports));
585      if (!ptrPrimitive)
586        continue;
587
588      nsCOMPtr<nsISupports> primitiveData;
589      ptrPrimitive->GetData(getter_AddRefs(primitiveData));
590
591      nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData));
592      if (!image) {
593        NS_WARNING("Image isn't an imgIContainer in transferable");
594        continue;
595      }
596
597      RefPtr<SourceSurface> surface =
598        image->GetFrame(imgIContainer::FRAME_CURRENT,
599                        imgIContainer::FLAG_SYNC_DECODE);
600      if (!surface) {
601        continue;
602      }
603      CGImageRef imageRef = NULL;
604      rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef);
605      if (NS_FAILED(rv) || !imageRef) {
606        continue;
607      }
608
609      // Convert the CGImageRef to TIFF data.
610      CFMutableDataRef tiffData = CFDataCreateMutable(kCFAllocatorDefault, 0);
611      CGImageDestinationRef destRef = CGImageDestinationCreateWithData(tiffData,
612                                                                       CFSTR("public.tiff"),
613                                                                       1,
614                                                                       NULL);
615      CGImageDestinationAddImage(destRef, imageRef, NULL);
616      bool successfullyConverted = CGImageDestinationFinalize(destRef);
617
618      CGImageRelease(imageRef);
619      if (destRef)
620        CFRelease(destRef);
621
622      if (!successfullyConverted) {
623        if (tiffData)
624          CFRelease(tiffData);
625        continue;
626      }
627
628      NSString* tiffType =
629        [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF];
630      [pasteboardOutputDict setObject:(NSMutableData*)tiffData
631                               forKey:tiffType];
632      if (tiffData)
633        CFRelease(tiffData);
634    }
635    else if (flavorStr.EqualsLiteral(kFileMime)) {
636      uint32_t len = 0;
637      nsCOMPtr<nsISupports> genericFile;
638      rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericFile), &len);
639      if (NS_FAILED(rv)) {
640        continue;
641      }
642
643      nsCOMPtr<nsIFile> file(do_QueryInterface(genericFile));
644      if (!file) {
645        nsCOMPtr<nsISupportsInterfacePointer> ptr(do_QueryInterface(genericFile));
646
647        if (ptr) {
648          ptr->GetData(getter_AddRefs(genericFile));
649          file = do_QueryInterface(genericFile);
650        }
651      }
652
653      if (!file) {
654        continue;
655      }
656
657      nsAutoString fileURI;
658      rv = file->GetPath(fileURI);
659      if (NS_FAILED(rv)) {
660        continue;
661      }
662
663      NSString* str = nsCocoaUtils::ToNSString(fileURI);
664      NSURL* url = [NSURL fileURLWithPath:str isDirectory:NO];
665      NSString* fileUTType =
666        [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL];
667      [pasteboardOutputDict setObject:[url absoluteString]
668                               forKey:fileUTType];
669    }
670    else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
671      NSString* urlPromise =
672        [UTIHelper stringFromPboardType:
673          (NSString*)kPasteboardTypeFileURLPromise];
674      NSString* urlPromiseContent =
675        [UTIHelper stringFromPboardType:
676          (NSString*)kPasteboardTypeFilePromiseContent];
677      [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""]
678                               forKey:urlPromise];
679      [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""]
680                               forKey:urlPromiseContent];
681    }
682    else if (flavorStr.EqualsLiteral(kURLMime)) {
683      uint32_t len = 0;
684      nsCOMPtr<nsISupports> genericURL;
685      rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericURL), &len);
686      nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
687
688      nsAutoString url;
689      urlObject->GetData(url);
690
691      NSString* nativeTitle = nil;
692
693      // A newline embedded in the URL means that the form is actually URL +
694      // title. This embedding occurs in nsDragService::GetData.
695      int32_t newlinePos = url.FindChar(char16_t('\n'));
696      if (newlinePos >= 0) {
697        url.Truncate(newlinePos);
698
699        nsAutoString urlTitle;
700        urlObject->GetData(urlTitle);
701        urlTitle.Mid(urlTitle, newlinePos + 1, len - (newlinePos + 1));
702
703        nativeTitle =
704          [NSString stringWithCharacters:
705            reinterpret_cast<const unichar*>(urlTitle.get())
706                                  length:urlTitle.Length()];
707      }
708      // The Finder doesn't like getting random binary data aka
709      // Unicode, so change it into an escaped URL containing only
710      // ASCII.
711      nsAutoCString utf8Data = NS_ConvertUTF16toUTF8(url.get(), url.Length());
712      nsAutoCString escData;
713      NS_EscapeURL(utf8Data.get(),
714                   utf8Data.Length(),
715                   esc_OnlyNonASCII|esc_AlwaysCopy,
716                   escData);
717
718      NSString* nativeURL = [NSString stringWithUTF8String:escData.get()];
719      NSString* publicUrl =
720        [UTIHelper stringFromPboardType:kPublicUrlPboardType];
721      [pasteboardOutputDict setObject:nativeURL forKey:publicUrl];
722      if (nativeTitle) {
723        NSArray* urlsAndTitles = @[@[nativeURL], @[nativeTitle]];
724        NSString* urlName =
725          [UTIHelper stringFromPboardType:kPublicUrlNamePboardType];
726        NSString* urlsWithTitles =
727          [UTIHelper stringFromPboardType:kUrlsWithTitlesPboardType];
728        [pasteboardOutputDict setObject:nativeTitle
729                                 forKey:urlName];
730        [pasteboardOutputDict setObject:urlsAndTitles
731                                 forKey:urlsWithTitles];
732      }
733    }
734    [pboardType release];
735    // If it wasn't a type that we recognize as exportable we don't put it on the system
736    // clipboard. We'll just access it from our cached transferable when we need it.
737  }
738
739  return pasteboardOutputDict;
740
741  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
742}
743
744// aPasteboardType is being retained and needs to be released by the caller.
745bool nsClipboard::IsStringType(const nsCString& aMIMEType, NSString** aPasteboardType)
746{
747  if (aMIMEType.EqualsLiteral(kUnicodeMime)) {
748    *aPasteboardType =
749      [[UTIHelper stringFromPboardType:NSPasteboardTypeString] retain];
750    return true;
751  } else if (aMIMEType.EqualsLiteral(kRTFMime)) {
752    *aPasteboardType =
753      [[UTIHelper stringFromPboardType:NSPasteboardTypeRTF] retain];
754    return true;
755  } else if (aMIMEType.EqualsLiteral(kHTMLMime)) {
756    *aPasteboardType =
757      [[UTIHelper stringFromPboardType:NSPasteboardTypeHTML] retain];
758    return true;
759  } else {
760    return false;
761  }
762}
763
764NSString* nsClipboard::WrapHtmlForSystemPasteboard(NSString* aString)
765{
766  NSString* wrapped =
767    [NSString stringWithFormat:
768      @"<html>"
769         "<head>"
770           "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">"
771         "</head>"
772         "<body>"
773           "%@"
774         "</body>"
775       "</html>", aString];
776  return wrapped;
777}
778
779/**
780  * Sets the transferable object
781  *
782  */
783NS_IMETHODIMP
784nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* anOwner,
785                     int32_t aWhichClipboard)
786{
787  NS_ASSERTION (aTransferable, "clipboard given a null transferable");
788
789  if (aWhichClipboard == kSelectionCache) {
790    if (aTransferable) {
791      SetSelectionCache(aTransferable);
792      return NS_OK;
793    }
794    return NS_ERROR_FAILURE;
795  }
796
797  if (aTransferable == mTransferable && anOwner == mClipboardOwner) {
798    return NS_OK;
799  }
800  bool selectClipPresent;
801  SupportsSelectionClipboard(&selectClipPresent);
802  bool findClipPresent;
803  SupportsFindClipboard(&findClipPresent);
804  if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard) {
805    return NS_ERROR_FAILURE;
806  }
807
808  EmptyClipboard(aWhichClipboard);
809
810  mClipboardOwner = anOwner;
811  mTransferable = aTransferable;
812
813  nsresult rv = NS_ERROR_FAILURE;
814  if (mTransferable) {
815    rv = SetNativeClipboardData(aWhichClipboard);
816  }
817
818  return rv;
819}
820
821/**
822  * Gets the transferable object
823  *
824  */
825NS_IMETHODIMP
826nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard)
827{
828  NS_ASSERTION (aTransferable, "clipboard given a null transferable");
829
830  bool selectClipPresent;
831  SupportsSelectionClipboard(&selectClipPresent);
832  bool findClipPresent;
833  SupportsFindClipboard(&findClipPresent);
834  if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard)
835    return NS_ERROR_FAILURE;
836
837  if (aTransferable) {
838    return GetNativeClipboardData(aTransferable, aWhichClipboard);
839  }
840
841  return NS_ERROR_FAILURE;
842}
843
844NS_IMETHODIMP
845nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
846{
847  if (aWhichClipboard == kSelectionCache) {
848    ClearSelectionCache();
849    return NS_OK;
850  }
851
852  bool selectClipPresent;
853  SupportsSelectionClipboard(&selectClipPresent);
854  bool findClipPresent;
855  SupportsFindClipboard(&findClipPresent);
856  if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard) {
857    return NS_ERROR_FAILURE;
858  }
859
860  if (mIgnoreEmptyNotification) {
861    return NS_OK;
862  }
863
864  if (mClipboardOwner) {
865    mClipboardOwner->LosingOwnership(mTransferable);
866    mClipboardOwner = nullptr;
867  }
868
869  mTransferable = nullptr;
870  return NS_OK;
871}
872
873NS_IMETHODIMP
874nsClipboard::SupportsSelectionClipboard(bool* _retval)
875{
876  *_retval = false;   // we don't support the selection clipboard by default.
877  return NS_OK;
878}
879