Bug 1700051: part 28) Refactor `WordSplitState<T>::GetDOMWordSeparatorOffset` to...
[gecko.git] / widget / cocoa / nsClipboard.mm
blob5a5e85ff168b94f35baf00afd083ac1b1d83c70a
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/. */
6 #include <algorithm>
8 #include "mozilla/Logging.h"
10 #include "mozilla/Unused.h"
12 #include "gfxPlatform.h"
13 #include "nsArrayUtils.h"
14 #include "nsCOMPtr.h"
15 #include "nsClipboard.h"
16 #include "nsString.h"
17 #include "nsISupportsPrimitives.h"
18 #include "nsPrimitiveHelpers.h"
19 #include "nsMemory.h"
20 #include "nsIFile.h"
21 #include "nsStringStream.h"
22 #include "nsDragService.h"
23 #include "nsEscape.h"
24 #include "nsPrintfCString.h"
25 #include "nsObjCExceptions.h"
26 #include "imgIContainer.h"
27 #include "nsCocoaUtils.h"
29 using mozilla::gfx::DataSourceSurface;
30 using mozilla::gfx::SourceSurface;
31 using mozilla::LogLevel;
33 extern mozilla::LazyLogModule sCocoaLog;
35 mozilla::StaticRefPtr<nsITransferable> nsClipboard::sSelectionCache;
37 @implementation UTIHelper
39 + (NSString*)stringFromPboardType:(NSString*)aType {
40   if ([aType isEqualToString:kMozWildcardPboardType] ||
41       [aType isEqualToString:kMozCustomTypesPboardType] ||
42       [aType isEqualToString:kPublicUrlPboardType] ||
43       [aType isEqualToString:kPublicUrlNamePboardType] ||
44       [aType isEqualToString:kMozFileUrlsPboardType] ||
45       [aType isEqualToString:(NSString*)kPasteboardTypeFileURLPromise] ||
46       [aType isEqualToString:(NSString*)kPasteboardTypeFilePromiseContent] ||
47       [aType isEqualToString:(NSString*)kUTTypeFileURL] ||
48       [aType isEqualToString:NSStringPboardType] ||
49       [aType isEqualToString:NSPasteboardTypeString] ||
50       [aType isEqualToString:NSPasteboardTypeHTML] || [aType isEqualToString:NSPasteboardTypeRTF] ||
51       [aType isEqualToString:NSPasteboardTypeTIFF] || [aType isEqualToString:NSPasteboardTypePNG]) {
52     return [NSString stringWithString:aType];
53   }
54   NSString* dynamicType = (NSString*)UTTypeCreatePreferredIdentifierForTag(
55       kUTTagClassNSPboardType, (CFStringRef)aType, kUTTypeData);
56   NSString* result = [NSString stringWithString:dynamicType];
57   [dynamicType release];
58   return result;
61 @end  // UTIHelper
63 nsClipboard::nsClipboard()
64     : mCachedClipboard(-1), mChangeCount(0), mIgnoreEmptyNotification(false) {}
66 nsClipboard::~nsClipboard() {
67   EmptyClipboard(kGlobalClipboard);
68   EmptyClipboard(kFindClipboard);
69   ClearSelectionCache();
72 NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
74 // We separate this into its own function because after an @try, all local
75 // variables within that function get marked as volatile, and our C++ type
76 // system doesn't like volatile things.
77 static NSData* GetDataFromPasteboard(NSPasteboard* aPasteboard, NSString* aType) {
78   NSData* data = nil;
79   @try {
80     data = [aPasteboard dataForType:aType];
81   } @catch (NSException* e) {
82     NS_WARNING(
83         nsPrintfCString("Exception raised while getting data from the pasteboard: \"%s - %s\"",
84                         [[e name] UTF8String], [[e reason] UTF8String])
85             .get());
86     mozilla::Unused << e;
87   }
88   return data;
91 void nsClipboard::SetSelectionCache(nsITransferable* aTransferable) {
92   sSelectionCache = aTransferable;
95 void nsClipboard::ClearSelectionCache() { sSelectionCache = nullptr; }
97 NS_IMETHODIMP
98 nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard) {
99   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
101   if ((aWhichClipboard != kGlobalClipboard && aWhichClipboard != kFindClipboard) || !mTransferable)
102     return NS_ERROR_FAILURE;
104   mIgnoreEmptyNotification = true;
106   NSDictionary* pasteboardOutputDict = PasteboardDictFromTransferable(mTransferable);
107   if (!pasteboardOutputDict) return NS_ERROR_FAILURE;
109   unsigned int outputCount = [pasteboardOutputDict count];
110   NSArray* outputKeys = [pasteboardOutputDict allKeys];
111   NSPasteboard* cocoaPasteboard;
112   if (aWhichClipboard == kFindClipboard) {
113     cocoaPasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
114     NSString* stringType = [UTIHelper stringFromPboardType:NSPasteboardTypeString];
115     [cocoaPasteboard declareTypes:[NSArray arrayWithObject:stringType] owner:nil];
116   } else {
117     // Write everything else out to the general pasteboard.
118     cocoaPasteboard = [NSPasteboard generalPasteboard];
119     [cocoaPasteboard declareTypes:outputKeys owner:nil];
120   }
122   for (unsigned int i = 0; i < outputCount; i++) {
123     NSString* currentKey = [outputKeys objectAtIndex:i];
124     id currentValue = [pasteboardOutputDict valueForKey:currentKey];
125     if (aWhichClipboard == kFindClipboard) {
126       if ([currentKey isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeString]]) {
127         [cocoaPasteboard setString:currentValue forType:currentKey];
128       }
129     } else {
130       if ([currentKey isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeString]] ||
131           [currentKey isEqualToString:[UTIHelper stringFromPboardType:kPublicUrlPboardType]] ||
132           [currentKey isEqualToString:[UTIHelper stringFromPboardType:kPublicUrlNamePboardType]]) {
133         [cocoaPasteboard setString:currentValue forType:currentKey];
134       } else if ([currentKey
135                      isEqualToString:[UTIHelper stringFromPboardType:kUrlsWithTitlesPboardType]]) {
136         [cocoaPasteboard setPropertyList:[pasteboardOutputDict valueForKey:currentKey]
137                                  forType:currentKey];
138       } else if ([currentKey
139                      isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeHTML]]) {
140         [cocoaPasteboard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
141                            forType:currentKey];
142       } else if ([currentKey
143                      isEqualToString:[UTIHelper stringFromPboardType:kMozFileUrlsPboardType]]) {
144         [cocoaPasteboard writeObjects:currentValue];
145       } else {
146         [cocoaPasteboard setData:currentValue forType:currentKey];
147       }
148     }
149   }
151   mCachedClipboard = aWhichClipboard;
152   mChangeCount = [cocoaPasteboard changeCount];
154   mIgnoreEmptyNotification = false;
156   return NS_OK;
158   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
161 nsresult nsClipboard::TransferableFromPasteboard(nsITransferable* aTransferable,
162                                                  NSPasteboard* cocoaPasteboard) {
163   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
165   // get flavor list that includes all acceptable flavors (including ones obtained through
166   // conversion)
167   nsTArray<nsCString> flavors;
168   nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
169   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
171   for (uint32_t i = 0; i < flavors.Length(); i++) {
172     nsCString& flavorStr = flavors[i];
174     // printf("looking for clipboard data of type %s\n", flavorStr.get());
176     NSString* pboardType = nil;
177     if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
178       NSString* pString = [cocoaPasteboard stringForType:pboardType];
179       if (!pString) {
180         [pboardType release];
181         continue;
182       }
184       NSData* stringData;
185       if ([pboardType isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeRTF]]) {
186         stringData = [pString dataUsingEncoding:NSASCIIStringEncoding];
187       } else {
188         stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
189       }
190       unsigned int dataLength = [stringData length];
191       void* clipboardDataPtr = malloc(dataLength);
192       if (!clipboardDataPtr) {
193         [pboardType release];
194         return NS_ERROR_OUT_OF_MEMORY;
195       }
196       [stringData getBytes:clipboardDataPtr length:dataLength];
198       // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
199       int32_t signedDataLength = dataLength;
200       nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr,
201                                                          &signedDataLength);
202       dataLength = signedDataLength;
204       // skip BOM (Byte Order Mark to distinguish little or big endian)
205       char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
206       if ((dataLength > 2) &&
207           ((clipboardDataPtrNoBOM[0] == 0xFEFF) || (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
208         dataLength -= sizeof(char16_t);
209         clipboardDataPtrNoBOM += 1;
210       }
212       nsCOMPtr<nsISupports> genericDataWrapper;
213       nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
214                                                  getter_AddRefs(genericDataWrapper));
215       aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
216       free(clipboardDataPtr);
217       [pboardType release];
218       break;
219     } else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
220       NSString* type = [cocoaPasteboard
221           availableTypeFromArray:
222               [NSArray arrayWithObject:[UTIHelper stringFromPboardType:kMozCustomTypesPboardType]]];
223       if (!type) {
224         continue;
225       }
227       NSData* pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type);
228       if (!pasteboardData) {
229         continue;
230       }
232       unsigned int dataLength = [pasteboardData length];
233       void* clipboardDataPtr = malloc(dataLength);
234       if (!clipboardDataPtr) {
235         return NS_ERROR_OUT_OF_MEMORY;
236       }
237       [pasteboardData getBytes:clipboardDataPtr length:dataLength];
239       nsCOMPtr<nsISupports> genericDataWrapper;
240       nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtr, dataLength,
241                                                  getter_AddRefs(genericDataWrapper));
243       aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
244       free(clipboardDataPtr);
245     } else if (flavorStr.EqualsLiteral(kJPEGImageMime) || flavorStr.EqualsLiteral(kJPGImageMime) ||
246                flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime)) {
247       // Figure out if there's data on the pasteboard we can grab (sanity check)
248       NSString* type = [cocoaPasteboard
249           availableTypeFromArray:
250               [NSArray arrayWithObjects:[UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL],
251                                         [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF],
252                                         [UTIHelper stringFromPboardType:NSPasteboardTypePNG], nil]];
253       if (!type) continue;
255       // Read data off the clipboard
256       NSData* pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type);
257       if (!pasteboardData) continue;
259       // Figure out what type we're converting to
260       CFStringRef outputType = NULL;
261       if (flavorStr.EqualsLiteral(kJPEGImageMime) || flavorStr.EqualsLiteral(kJPGImageMime))
262         outputType = CFSTR("public.jpeg");
263       else if (flavorStr.EqualsLiteral(kPNGImageMime))
264         outputType = CFSTR("public.png");
265       else if (flavorStr.EqualsLiteral(kGIFImageMime))
266         outputType = CFSTR("com.compuserve.gif");
267       else
268         continue;
270       // Use ImageIO to interpret the data on the clipboard and transcode.
271       // Note that ImageIO, like all CF APIs, allows NULLs to propagate freely
272       // and safely in most cases (like ObjC). A notable exception is CFRelease.
273       NSDictionary* options = [NSDictionary
274           dictionaryWithObjectsAndKeys:(NSNumber*)kCFBooleanTrue, kCGImageSourceShouldAllowFloat,
275                                        type, kCGImageSourceTypeIdentifierHint, nil];
276       CGImageSourceRef source = nullptr;
277       if (type == [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL]) {
278         NSString* urlStr = [cocoaPasteboard stringForType:type];
279         NSURL* url = [NSURL URLWithString:urlStr];
280         source = CGImageSourceCreateWithURL((CFURLRef)url, (CFDictionaryRef)options);
281       } else {
282         source = CGImageSourceCreateWithData((CFDataRef)pasteboardData, (CFDictionaryRef)options);
283       }
285       NSMutableData* encodedData = [NSMutableData data];
286       CGImageDestinationRef dest =
287           CGImageDestinationCreateWithData((CFMutableDataRef)encodedData, outputType, 1, NULL);
288       CGImageDestinationAddImageFromSource(dest, source, 0, NULL);
289       bool successfullyConverted = CGImageDestinationFinalize(dest);
291       if (successfullyConverted) {
292         // Put the converted data in a form Gecko can understand
293         nsCOMPtr<nsIInputStream> byteStream;
294         NS_NewByteInputStream(getter_AddRefs(byteStream),
295                               mozilla::Span((const char*)[encodedData bytes], [encodedData length]),
296                               NS_ASSIGNMENT_COPY);
298         aTransferable->SetTransferData(flavorStr.get(), byteStream);
299       }
301       if (dest) CFRelease(dest);
302       if (source) CFRelease(source);
304       if (successfullyConverted)
305         break;
306       else
307         continue;
308     }
309   }
311   return NS_OK;
313   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
316 NS_IMETHODIMP
317 nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
318   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
320   if ((aWhichClipboard != kGlobalClipboard && aWhichClipboard != kFindClipboard) || !aTransferable)
321     return NS_ERROR_FAILURE;
323   NSPasteboard* cocoaPasteboard;
324   if (aWhichClipboard == kFindClipboard) {
325     cocoaPasteboard = [NSPasteboard pasteboardWithName:NSFindPboard];
326   } else {
327     cocoaPasteboard = [NSPasteboard generalPasteboard];
328   }
329   if (!cocoaPasteboard) return NS_ERROR_FAILURE;
331   // get flavor list that includes all acceptable flavors (including ones obtained through
332   // conversion)
333   nsTArray<nsCString> flavors;
334   nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
335   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
337   // If we were the last ones to put something on the pasteboard, then just use the cached
338   // transferable. Otherwise clear it because it isn't relevant any more.
339   if (mCachedClipboard == aWhichClipboard && mChangeCount == [cocoaPasteboard changeCount]) {
340     if (mTransferable) {
341       for (uint32_t i = 0; i < flavors.Length(); i++) {
342         nsCString& flavorStr = flavors[i];
344         nsCOMPtr<nsISupports> dataSupports;
345         rv = mTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(dataSupports));
346         if (NS_SUCCEEDED(rv)) {
347           aTransferable->SetTransferData(flavorStr.get(), dataSupports);
348           return NS_OK;  // maybe try to fill in more types? Is there a point?
349         }
350       }
351     }
352   } else {
353     EmptyClipboard(aWhichClipboard);
354   }
356   // at this point we can't satisfy the request from cache data so let's look
357   // for things other people put on the system clipboard
359   return nsClipboard::TransferableFromPasteboard(aTransferable, cocoaPasteboard);
361   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
364 // returns true if we have *any* of the passed in flavors available for pasting
365 NS_IMETHODIMP
366 nsClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
367                                     bool* outResult) {
368   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
370   *outResult = false;
372   if (aWhichClipboard != kGlobalClipboard) return NS_OK;
374   // first see if we have data for this in our cached transferable
375   if (mTransferable) {
376     nsTArray<nsCString> flavors;
377     nsresult rv = mTransferable->FlavorsTransferableCanImport(flavors);
378     if (NS_SUCCEEDED(rv)) {
379       for (uint32_t j = 0; j < flavors.Length(); j++) {
380         const nsCString& transferableFlavorStr = flavors[j];
382         for (uint32_t k = 0; k < aFlavorList.Length(); k++) {
383           if (transferableFlavorStr.Equals(aFlavorList[k])) {
384             *outResult = true;
385             return NS_OK;
386           }
387         }
388       }
389     }
390   }
392   NSPasteboard* generalPBoard = [NSPasteboard generalPasteboard];
394   for (auto& mimeType : aFlavorList) {
395     NSString* pboardType = nil;
396     if (nsClipboard::IsStringType(mimeType, &pboardType)) {
397       NSString* availableType =
398           [generalPBoard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
399       if (availableType && [availableType isEqualToString:pboardType]) {
400         [pboardType release];
401         *outResult = true;
402         break;
403       }
404       [pboardType release];
405     } else if (mimeType.EqualsLiteral(kCustomTypesMime)) {
406       NSString* availableType = [generalPBoard
407           availableTypeFromArray:
408               [NSArray arrayWithObject:[UTIHelper stringFromPboardType:kMozCustomTypesPboardType]]];
409       if (availableType) {
410         *outResult = true;
411         break;
412       }
413     } else if (mimeType.EqualsLiteral(kJPEGImageMime) || mimeType.EqualsLiteral(kJPGImageMime) ||
414                mimeType.EqualsLiteral(kPNGImageMime) || mimeType.EqualsLiteral(kGIFImageMime)) {
415       NSString* availableType = [generalPBoard
416           availableTypeFromArray:
417               [NSArray arrayWithObjects:[UTIHelper stringFromPboardType:NSPasteboardTypeTIFF],
418                                         [UTIHelper stringFromPboardType:NSPasteboardTypePNG], nil]];
419       if (availableType) {
420         *outResult = true;
421         break;
422       }
423     }
424   }
426   return NS_OK;
428   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
431 NS_IMETHODIMP
432 nsClipboard::SupportsFindClipboard(bool* _retval) {
433   NS_ENSURE_ARG_POINTER(_retval);
434   *_retval = true;
435   return NS_OK;
438 // static
439 mozilla::Maybe<uint32_t> nsClipboard::FindIndexOfImageFlavor(
440     const nsTArray<nsCString>& aMIMETypes) {
441   for (uint32_t i = 0; i < aMIMETypes.Length(); ++i) {
442     if (nsClipboard::IsImageType(aMIMETypes[i])) {
443       return mozilla::Some(i);
444     }
445   }
447   return mozilla::Nothing();
450 // This function converts anything that other applications might understand into the system format
451 // and puts it into a dictionary which it returns.
452 // static
453 NSDictionary* nsClipboard::PasteboardDictFromTransferable(nsITransferable* aTransferable) {
454   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
456   if (!aTransferable) {
457     return nil;
458   }
460   NSMutableDictionary* pasteboardOutputDict = [NSMutableDictionary dictionary];
462   nsTArray<nsCString> flavors;
463   nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors);
464   if (NS_FAILED(rv)) {
465     return nil;
466   }
468   const mozilla::Maybe<uint32_t> imageFlavorIndex = nsClipboard::FindIndexOfImageFlavor(flavors);
470   if (imageFlavorIndex) {
471     // When right-clicking and "Copy Image" is clicked on macOS, some apps expect the
472     // first flavor to be the image flavor. See bug 1689992. For other apps, the
473     // order shouldn't matter.
474     std::swap(*flavors.begin(), flavors[*imageFlavorIndex]);
475   }
476   for (uint32_t i = 0; i < flavors.Length(); i++) {
477     nsCString& flavorStr = flavors[i];
479     MOZ_LOG(sCocoaLog, LogLevel::Info,
480             ("writing out clipboard data of type %s (%d)\n", flavorStr.get(), i));
482     NSString* pboardType = nil;
483     if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
484       nsCOMPtr<nsISupports> genericDataWrapper;
485       rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericDataWrapper));
486       if (NS_FAILED(rv)) {
487         [pboardType release];
488         continue;
489       }
491       nsAutoString data;
492       if (nsCOMPtr<nsISupportsString> text = do_QueryInterface(genericDataWrapper)) {
493         text->GetData(data);
494       }
496       NSString* nativeString;
497       if (!data.IsEmpty()) {
498         nativeString = [NSString stringWithCharacters:(const unichar*)data.get()
499                                                length:data.Length()];
500       } else {
501         nativeString = [NSString string];
502       }
504       // be nice to Carbon apps, normalize the receiver's contents using Form C.
505       nativeString = [nativeString precomposedStringWithCanonicalMapping];
506       if (nativeString) {
507         [pasteboardOutputDict setObject:nativeString forKey:pboardType];
508       }
509       [pboardType release];
510     } else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
511       nsCOMPtr<nsISupports> genericDataWrapper;
512       rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericDataWrapper));
513       if (NS_FAILED(rv)) {
514         continue;
515       }
517       nsAutoCString data;
518       if (nsCOMPtr<nsISupportsCString> text = do_QueryInterface(genericDataWrapper)) {
519         text->GetData(data);
520       }
522       if (!data.IsEmpty()) {
523         NSData* nativeData = [NSData dataWithBytes:data.get() length:data.Length()];
524         NSString* customType = [UTIHelper stringFromPboardType:kMozCustomTypesPboardType];
525         if (!nativeData) {
526           continue;
527         }
528         [pasteboardOutputDict setObject:nativeData forKey:customType];
529       }
530     } else if (nsClipboard::IsImageType(flavorStr)) {
531       nsCOMPtr<nsISupports> transferSupports;
532       rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(transferSupports));
533       if (NS_FAILED(rv)) {
534         continue;
535       }
537       nsCOMPtr<imgIContainer> image(do_QueryInterface(transferSupports));
538       if (!image) {
539         NS_WARNING("Image isn't an imgIContainer in transferable");
540         continue;
541       }
543       RefPtr<SourceSurface> surface =
544           image->GetFrame(imgIContainer::FRAME_CURRENT,
545                           imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
546       if (!surface) {
547         continue;
548       }
549       CGImageRef imageRef = NULL;
550       rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef);
551       if (NS_FAILED(rv) || !imageRef) {
552         continue;
553       }
555       // Convert the CGImageRef to TIFF data.
556       CFMutableDataRef tiffData = CFDataCreateMutable(kCFAllocatorDefault, 0);
557       CGImageDestinationRef destRef =
558           CGImageDestinationCreateWithData(tiffData, CFSTR("public.tiff"), 1, NULL);
559       CGImageDestinationAddImage(destRef, imageRef, NULL);
560       bool successfullyConverted = CGImageDestinationFinalize(destRef);
562       CGImageRelease(imageRef);
563       if (destRef) {
564         CFRelease(destRef);
565       }
567       if (!successfullyConverted || !tiffData) {
568         if (tiffData) {
569           CFRelease(tiffData);
570         }
571         continue;
572       }
574       NSString* tiffType = [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF];
575       [pasteboardOutputDict setObject:(NSMutableData*)tiffData forKey:tiffType];
576       CFRelease(tiffData);
577     } else if (flavorStr.EqualsLiteral(kFileMime)) {
578       nsCOMPtr<nsISupports> genericFile;
579       rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericFile));
580       if (NS_FAILED(rv)) {
581         continue;
582       }
584       nsCOMPtr<nsIFile> file(do_QueryInterface(genericFile));
585       if (!file) {
586         continue;
587       }
589       nsAutoString fileURI;
590       rv = file->GetPath(fileURI);
591       if (NS_FAILED(rv)) {
592         continue;
593       }
595       NSString* str = nsCocoaUtils::ToNSString(fileURI);
596       NSURL* url = [NSURL fileURLWithPath:str isDirectory:NO];
597       NSString* fileUTType = [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL];
598       if (!url || ![url absoluteString]) {
599         continue;
600       }
601       [pasteboardOutputDict setObject:[url absoluteString] forKey:fileUTType];
602     } else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
603       NSString* urlPromise =
604           [UTIHelper stringFromPboardType:(NSString*)kPasteboardTypeFileURLPromise];
605       NSString* urlPromiseContent =
606           [UTIHelper stringFromPboardType:(NSString*)kPasteboardTypeFilePromiseContent];
607       [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""] forKey:urlPromise];
608       [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""] forKey:urlPromiseContent];
609     } else if (flavorStr.EqualsLiteral(kURLMime)) {
610       nsCOMPtr<nsISupports> genericURL;
611       rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericURL));
612       nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
614       nsAutoString url;
615       urlObject->GetData(url);
617       NSString* nativeTitle = nil;
619       // A newline embedded in the URL means that the form is actually URL +
620       // title. This embedding occurs in nsDragService::GetData.
621       int32_t newlinePos = url.FindChar(char16_t('\n'));
622       if (newlinePos >= 0) {
623         url.Truncate(newlinePos);
625         nsAutoString urlTitle;
626         urlObject->GetData(urlTitle);
627         urlTitle.Mid(urlTitle, newlinePos + 1, urlTitle.Length() - (newlinePos + 1));
629         nativeTitle =
630             [NSString stringWithCharacters:reinterpret_cast<const unichar*>(urlTitle.get())
631                                     length:urlTitle.Length()];
632       }
633       // The Finder doesn't like getting random binary data aka
634       // Unicode, so change it into an escaped URL containing only
635       // ASCII.
636       nsAutoCString utf8Data = NS_ConvertUTF16toUTF8(url.get(), url.Length());
637       nsAutoCString escData;
638       NS_EscapeURL(utf8Data.get(), utf8Data.Length(), esc_OnlyNonASCII | esc_AlwaysCopy, escData);
640       NSString* nativeURL = [NSString stringWithUTF8String:escData.get()];
641       NSString* publicUrl = [UTIHelper stringFromPboardType:kPublicUrlPboardType];
642       if (!nativeURL) {
643         continue;
644       }
645       [pasteboardOutputDict setObject:nativeURL forKey:publicUrl];
646       if (nativeTitle) {
647         NSArray* urlsAndTitles = @[ @[ nativeURL ], @[ nativeTitle ] ];
648         NSString* urlName = [UTIHelper stringFromPboardType:kPublicUrlNamePboardType];
649         NSString* urlsWithTitles = [UTIHelper stringFromPboardType:kUrlsWithTitlesPboardType];
650         [pasteboardOutputDict setObject:nativeTitle forKey:urlName];
651         [pasteboardOutputDict setObject:urlsAndTitles forKey:urlsWithTitles];
652       }
653     }
654     // If it wasn't a type that we recognize as exportable we don't put it on the system
655     // clipboard. We'll just access it from our cached transferable when we need it.
656   }
658   return pasteboardOutputDict;
660   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
663 // aPasteboardType is being retained and needs to be released by the caller.
664 bool nsClipboard::IsStringType(const nsCString& aMIMEType, NSString** aPasteboardType) {
665   if (aMIMEType.EqualsLiteral(kUnicodeMime)) {
666     *aPasteboardType = [[UTIHelper stringFromPboardType:NSPasteboardTypeString] retain];
667     return true;
668   } else if (aMIMEType.EqualsLiteral(kRTFMime)) {
669     *aPasteboardType = [[UTIHelper stringFromPboardType:NSPasteboardTypeRTF] retain];
670     return true;
671   } else if (aMIMEType.EqualsLiteral(kHTMLMime)) {
672     *aPasteboardType = [[UTIHelper stringFromPboardType:NSPasteboardTypeHTML] retain];
673     return true;
674   } else {
675     return false;
676   }
679 // static
680 bool nsClipboard::IsImageType(const nsACString& aMIMEType) {
681   return aMIMEType.EqualsLiteral(kPNGImageMime) || aMIMEType.EqualsLiteral(kJPEGImageMime) ||
682          aMIMEType.EqualsLiteral(kJPGImageMime) || aMIMEType.EqualsLiteral(kGIFImageMime) ||
683          aMIMEType.EqualsLiteral(kNativeImageMime);
686 NSString* nsClipboard::WrapHtmlForSystemPasteboard(NSString* aString) {
687   NSString* wrapped = [NSString
688       stringWithFormat:@"<html>"
689                         "<head>"
690                         "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">"
691                         "</head>"
692                         "<body>"
693                         "%@"
694                         "</body>"
695                         "</html>",
696                        aString];
697   return wrapped;
701  * Sets the transferable object
703  */
704 NS_IMETHODIMP
705 nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* anOwner,
706                      int32_t aWhichClipboard) {
707   NS_ASSERTION(aTransferable, "clipboard given a null transferable");
709   if (aWhichClipboard == kSelectionCache) {
710     if (aTransferable) {
711       SetSelectionCache(aTransferable);
712       return NS_OK;
713     }
714     return NS_ERROR_FAILURE;
715   }
717   if (aTransferable == mTransferable && anOwner == mClipboardOwner) {
718     return NS_OK;
719   }
720   bool selectClipPresent;
721   SupportsSelectionClipboard(&selectClipPresent);
722   bool findClipPresent;
723   SupportsFindClipboard(&findClipPresent);
724   if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard) {
725     return NS_ERROR_FAILURE;
726   }
728   EmptyClipboard(aWhichClipboard);
730   mClipboardOwner = anOwner;
731   mTransferable = aTransferable;
733   nsresult rv = NS_ERROR_FAILURE;
734   if (mTransferable) {
735     rv = SetNativeClipboardData(aWhichClipboard);
736   }
738   return rv;
742  * Gets the transferable object
744  */
745 NS_IMETHODIMP
746 nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
747   NS_ASSERTION(aTransferable, "clipboard given a null transferable");
749   bool selectClipPresent;
750   SupportsSelectionClipboard(&selectClipPresent);
751   bool findClipPresent;
752   SupportsFindClipboard(&findClipPresent);
753   if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard)
754     return NS_ERROR_FAILURE;
756   if (aTransferable) {
757     return GetNativeClipboardData(aTransferable, aWhichClipboard);
758   }
760   return NS_ERROR_FAILURE;
763 NS_IMETHODIMP
764 nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
765   if (aWhichClipboard == kSelectionCache) {
766     ClearSelectionCache();
767     return NS_OK;
768   }
770   bool selectClipPresent;
771   SupportsSelectionClipboard(&selectClipPresent);
772   bool findClipPresent;
773   SupportsFindClipboard(&findClipPresent);
774   if (!selectClipPresent && !findClipPresent && aWhichClipboard != kGlobalClipboard) {
775     return NS_ERROR_FAILURE;
776   }
778   if (mIgnoreEmptyNotification) {
779     return NS_OK;
780   }
782   if (mClipboardOwner) {
783     mClipboardOwner->LosingOwnership(mTransferable);
784     mClipboardOwner = nullptr;
785   }
787   mTransferable = nullptr;
788   return NS_OK;
791 NS_IMETHODIMP
792 nsClipboard::SupportsSelectionClipboard(bool* _retval) {
793   *_retval = false;  // we don't support the selection clipboard by default.
794   return NS_OK;