Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / cocoa / nsClipboard.mm
blob1c4788460c6f3e1522609c4c62849b1d489649bb
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/gfx/2D.h"
9 #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 "nsIFile.h"
20 #include "nsStringStream.h"
21 #include "nsEscape.h"
22 #include "nsPrintfCString.h"
23 #include "nsObjCExceptions.h"
24 #include "imgIContainer.h"
25 #include "nsCocoaUtils.h"
27 using mozilla::gfx::DataSourceSurface;
28 using mozilla::gfx::SourceSurface;
30 mozilla::StaticRefPtr<nsITransferable> nsClipboard::sSelectionCache;
31 int32_t nsClipboard::sSelectionCacheChangeCount = 0;
33 nsClipboard::nsClipboard()
34     : nsBaseClipboard(mozilla::dom::ClipboardCapabilities(
35           false /* supportsSelectionClipboard */,
36           true /* supportsFindClipboard */,
37           true /* supportsSelectionCache */)) {}
39 nsClipboard::~nsClipboard() { ClearSelectionCache(); }
41 NS_IMPL_ISUPPORTS_INHERITED0(nsClipboard, nsBaseClipboard)
43 namespace {
45 // We separate this into its own function because after an @try, all local
46 // variables within that function get marked as volatile, and our C++ type
47 // system doesn't like volatile things.
48 static NSData* GetDataFromPasteboard(NSPasteboard* aPasteboard,
49                                      NSString* aType) {
50   NSData* data = nil;
51   @try {
52     data = [aPasteboard dataForType:aType];
53   } @catch (NSException* e) {
54     NS_WARNING(nsPrintfCString("Exception raised while getting data from the "
55                                "pasteboard: \"%s - %s\"",
56                                [[e name] UTF8String], [[e reason] UTF8String])
57                    .get());
58     mozilla::Unused << e;
59   }
60   return data;
63 static NSPasteboard* GetPasteboard(int32_t aWhichClipboard) {
64   switch (aWhichClipboard) {
65     case nsIClipboard::kGlobalClipboard:
66       return [NSPasteboard generalPasteboard];
67     case nsIClipboard::kFindClipboard:
68       return [NSPasteboard pasteboardWithName:NSPasteboardNameFind];
69     default:
70       return nil;
71   }
74 }  // namespace
76 void nsClipboard::SetSelectionCache(nsITransferable* aTransferable) {
77   sSelectionCacheChangeCount++;
78   sSelectionCache = aTransferable;
81 void nsClipboard::ClearSelectionCache() { SetSelectionCache(nullptr); }
83 NS_IMETHODIMP
84 nsClipboard::SetNativeClipboardData(nsITransferable* aTransferable,
85                                     int32_t aWhichClipboard) {
86   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
88   MOZ_ASSERT(aTransferable);
89   MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
91   if (aWhichClipboard == kSelectionCache) {
92     SetSelectionCache(aTransferable);
93     return NS_OK;
94   }
96   NSDictionary* pasteboardOutputDict =
97       PasteboardDictFromTransferable(aTransferable);
98   if (!pasteboardOutputDict) return NS_ERROR_FAILURE;
100   unsigned int outputCount = [pasteboardOutputDict count];
101   NSArray* outputKeys = [pasteboardOutputDict allKeys];
102   NSPasteboard* cocoaPasteboard = GetPasteboard(aWhichClipboard);
103   MOZ_ASSERT(cocoaPasteboard);
104   if (aWhichClipboard == kFindClipboard) {
105     NSString* stringType =
106         [UTIHelper stringFromPboardType:NSPasteboardTypeString];
107     [cocoaPasteboard declareTypes:[NSArray arrayWithObject:stringType]
108                             owner:nil];
109   } else {
110     // Write everything else out to the general pasteboard.
111     MOZ_ASSERT(aWhichClipboard == kGlobalClipboard);
112     [cocoaPasteboard declareTypes:outputKeys owner:nil];
113   }
115   for (unsigned int i = 0; i < outputCount; i++) {
116     NSString* currentKey = [outputKeys objectAtIndex:i];
117     id currentValue = [pasteboardOutputDict valueForKey:currentKey];
118     if (aWhichClipboard == kFindClipboard) {
119       if ([currentKey isEqualToString:[UTIHelper stringFromPboardType:
120                                                      NSPasteboardTypeString]]) {
121         [cocoaPasteboard setString:currentValue forType:currentKey];
122       }
123     } else {
124       if ([currentKey isEqualToString:[UTIHelper stringFromPboardType:
125                                                      NSPasteboardTypeString]] ||
126           [currentKey
127               isEqualToString:[UTIHelper
128                                   stringFromPboardType:kPublicUrlPboardType]] ||
129           [currentKey
130               isEqualToString:
131                   [UTIHelper stringFromPboardType:kPublicUrlNamePboardType]]) {
132         [cocoaPasteboard setString:currentValue forType:currentKey];
133       } else if ([currentKey
134                      isEqualToString:
135                          [UTIHelper
136                              stringFromPboardType:kUrlsWithTitlesPboardType]]) {
137         [cocoaPasteboard
138             setPropertyList:[pasteboardOutputDict valueForKey:currentKey]
139                     forType:currentKey];
140       } else if ([currentKey
141                      isEqualToString:[UTIHelper stringFromPboardType:
142                                                     NSPasteboardTypeHTML]]) {
143         [cocoaPasteboard
144             setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
145               forType:currentKey];
146       } else if ([currentKey
147                      isEqualToString:[UTIHelper stringFromPboardType:
148                                                     kMozFileUrlsPboardType]]) {
149         [cocoaPasteboard writeObjects:currentValue];
150       } else if ([currentKey
151                      isEqualToString:
152                          [UTIHelper
153                              stringFromPboardType:(NSString*)kUTTypeFileURL]]) {
154         [cocoaPasteboard setString:currentValue forType:currentKey];
155       } else if ([currentKey
156                      isEqualToString:
157                          [UTIHelper
158                              stringFromPboardType:kPasteboardConcealedType]]) {
159         // It's fine to set the data to null for this field - this field is an
160         // addition to a value's other type and works like a flag.
161         [cocoaPasteboard setData:NULL forType:currentKey];
162       } else {
163         [cocoaPasteboard setData:currentValue forType:currentKey];
164       }
165     }
166   }
168   return NS_OK;
170   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
173 nsresult nsClipboard::TransferableFromPasteboard(
174     nsITransferable* aTransferable, NSPasteboard* cocoaPasteboard) {
175   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
177   // get flavor list that includes all acceptable flavors (including ones
178   // obtained through conversion)
179   nsTArray<nsCString> flavors;
180   nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
181   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
183   for (uint32_t i = 0; i < flavors.Length(); i++) {
184     nsCString& flavorStr = flavors[i];
186     // printf("looking for clipboard data of type %s\n", flavorStr.get());
188     NSString* pboardType = nil;
189     if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
190       NSString* pString = [cocoaPasteboard stringForType:pboardType];
191       if (!pString) {
192         continue;
193       }
195       NSData* stringData;
196       bool isRTF = [pboardType
197           isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeRTF]];
198       if (isRTF) {
199         stringData = [pString dataUsingEncoding:NSASCIIStringEncoding];
200       } else {
201         stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
202       }
203       unsigned int dataLength = [stringData length];
204       void* clipboardDataPtr = malloc(dataLength);
205       if (!clipboardDataPtr) {
206         return NS_ERROR_OUT_OF_MEMORY;
207       }
208       [stringData getBytes:clipboardDataPtr length:dataLength];
210       // The DOM only wants LF, so convert from MacOS line endings to DOM line
211       // endings.
212       int32_t signedDataLength = dataLength;
213       nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(
214           isRTF, &clipboardDataPtr, &signedDataLength);
215       dataLength = signedDataLength;
217       // skip BOM (Byte Order Mark to distinguish little or big endian)
218       char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
219       if ((dataLength > 2) && ((clipboardDataPtrNoBOM[0] == 0xFEFF) ||
220                                (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
221         dataLength -= sizeof(char16_t);
222         clipboardDataPtrNoBOM += 1;
223       }
225       nsCOMPtr<nsISupports> genericDataWrapper;
226       nsPrimitiveHelpers::CreatePrimitiveForData(
227           flavorStr, clipboardDataPtrNoBOM, dataLength,
228           getter_AddRefs(genericDataWrapper));
229       aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
230       free(clipboardDataPtr);
231       break;
232     } else if (flavorStr.EqualsLiteral(kFileMime)) {
233       NSArray* items = [cocoaPasteboard pasteboardItems];
234       if (!items || [items count] <= 0) {
235         continue;
236       }
238       // XXX we don't support multiple clipboard item on DOM and XPCOM interface
239       // for now, so we only get the data from the first pasteboard item.
240       NSPasteboardItem* item = [items objectAtIndex:0];
241       if (!item) {
242         continue;
243       }
245       nsCocoaUtils::SetTransferDataForTypeFromPasteboardItem(aTransferable,
246                                                              flavorStr, item);
247     } else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
248       NSString* type = [cocoaPasteboard
249           availableTypeFromArray:
250               [NSArray
251                   arrayWithObject:[UTIHelper stringFromPboardType:
252                                                  kMozCustomTypesPboardType]]];
253       if (!type) {
254         continue;
255       }
257       NSData* pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type);
258       if (!pasteboardData) {
259         continue;
260       }
262       unsigned int dataLength = [pasteboardData length];
263       void* clipboardDataPtr = malloc(dataLength);
264       if (!clipboardDataPtr) {
265         return NS_ERROR_OUT_OF_MEMORY;
266       }
267       [pasteboardData getBytes:clipboardDataPtr length:dataLength];
269       nsCOMPtr<nsISupports> genericDataWrapper;
270       nsPrimitiveHelpers::CreatePrimitiveForData(
271           flavorStr, clipboardDataPtr, dataLength,
272           getter_AddRefs(genericDataWrapper));
274       aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
275       free(clipboardDataPtr);
276     } else if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
277                flavorStr.EqualsLiteral(kJPGImageMime) ||
278                flavorStr.EqualsLiteral(kPNGImageMime) ||
279                flavorStr.EqualsLiteral(kGIFImageMime)) {
280       // Figure out if there's data on the pasteboard we can grab (sanity check)
281       NSString* type = [cocoaPasteboard
282           availableTypeFromArray:
283               [NSArray
284                   arrayWithObjects:
285                       [UTIHelper
286                           stringFromPboardType:(NSString*)kUTTypeFileURL],
287                       [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF],
288                       [UTIHelper stringFromPboardType:NSPasteboardTypePNG],
289                       nil]];
290       if (!type) continue;
292       // Read data off the clipboard
293       NSData* pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type);
294       if (!pasteboardData) continue;
296       // Figure out what type we're converting to
297       CFStringRef outputType = NULL;
298       if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
299           flavorStr.EqualsLiteral(kJPGImageMime))
300         outputType = CFSTR("public.jpeg");
301       else if (flavorStr.EqualsLiteral(kPNGImageMime))
302         outputType = CFSTR("public.png");
303       else if (flavorStr.EqualsLiteral(kGIFImageMime))
304         outputType = CFSTR("com.compuserve.gif");
305       else
306         continue;
308       // Use ImageIO to interpret the data on the clipboard and transcode.
309       // Note that ImageIO, like all CF APIs, allows NULLs to propagate freely
310       // and safely in most cases (like ObjC). A notable exception is CFRelease.
311       NSDictionary* options = [NSDictionary
312           dictionaryWithObjectsAndKeys:(NSNumber*)kCFBooleanTrue,
313                                        kCGImageSourceShouldAllowFloat, type,
314                                        kCGImageSourceTypeIdentifierHint, nil];
315       CGImageSourceRef source = nullptr;
316       if (type == [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL]) {
317         NSString* urlStr = [cocoaPasteboard stringForType:type];
318         NSURL* url = [NSURL URLWithString:urlStr];
319         source =
320             CGImageSourceCreateWithURL((CFURLRef)url, (CFDictionaryRef)options);
321       } else {
322         source = CGImageSourceCreateWithData((CFDataRef)pasteboardData,
323                                              (CFDictionaryRef)options);
324       }
326       NSMutableData* encodedData = [NSMutableData data];
327       CGImageDestinationRef dest = CGImageDestinationCreateWithData(
328           (CFMutableDataRef)encodedData, outputType, 1, NULL);
329       CGImageDestinationAddImageFromSource(dest, source, 0, NULL);
330       bool successfullyConverted = CGImageDestinationFinalize(dest);
332       if (successfullyConverted) {
333         // Put the converted data in a form Gecko can understand
334         nsCOMPtr<nsIInputStream> byteStream;
335         NS_NewByteInputStream(getter_AddRefs(byteStream),
336                               mozilla::Span((const char*)[encodedData bytes],
337                                             [encodedData length]),
338                               NS_ASSIGNMENT_COPY);
340         aTransferable->SetTransferData(flavorStr.get(), byteStream);
341       }
343       if (dest) CFRelease(dest);
344       if (source) CFRelease(source);
346       if (successfullyConverted) {
347         // XXX Maybe try to fill in more types? Is there a point?
348         break;
349       } else {
350         continue;
351       }
352     }
353   }
355   return NS_OK;
357   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
360 NS_IMETHODIMP
361 nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable,
362                                     int32_t aWhichClipboard) {
363   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
365   MOZ_DIAGNOSTIC_ASSERT(aTransferable);
366   MOZ_DIAGNOSTIC_ASSERT(
367       nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
369   if (kSelectionCache == aWhichClipboard) {
370     if (!sSelectionCache) {
371       return NS_OK;
372     }
374     // get flavor list that includes all acceptable flavors (including ones
375     // obtained through conversion)
376     nsTArray<nsCString> flavors;
377     nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
378     if (NS_FAILED(rv)) {
379       return NS_ERROR_FAILURE;
380     }
382     for (const auto& flavor : flavors) {
383       nsCOMPtr<nsISupports> dataSupports;
384       rv = sSelectionCache->GetTransferData(flavor.get(),
385                                             getter_AddRefs(dataSupports));
386       if (NS_SUCCEEDED(rv)) {
387         MOZ_CLIPBOARD_LOG("%s: getting %s from cache.", __FUNCTION__,
388                           flavor.get());
389         aTransferable->SetTransferData(flavor.get(), dataSupports);
390         // XXX Maybe try to fill in more types? Is there a point?
391         break;
392       }
393     }
394     return NS_OK;
395   }
397   NSPasteboard* cocoaPasteboard = GetPasteboard(aWhichClipboard);
398   if (!cocoaPasteboard) {
399     return NS_ERROR_FAILURE;
400   }
402   return TransferableFromPasteboard(aTransferable, cocoaPasteboard);
404   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
407 // returns true if we have *any* of the passed in flavors available for pasting
408 mozilla::Result<bool, nsresult>
409 nsClipboard::HasNativeClipboardDataMatchingFlavors(
410     const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard) {
411   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
413   MOZ_DIAGNOSTIC_ASSERT(
414       nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
416   if (kSelectionCache == aWhichClipboard) {
417     nsTArray<nsCString> transferableFlavors;
418     if (sSelectionCache &&
419         NS_SUCCEEDED(sSelectionCache->FlavorsTransferableCanExport(
420             transferableFlavors))) {
421       if (MOZ_CLIPBOARD_LOG_ENABLED()) {
422         MOZ_CLIPBOARD_LOG("    SelectionCache types (nums %zu)\n",
423                           transferableFlavors.Length());
424         for (const auto& transferableFlavor : transferableFlavors) {
425           MOZ_CLIPBOARD_LOG("        MIME %s", transferableFlavor.get());
426         }
427       }
429       for (const auto& transferableFlavor : transferableFlavors) {
430         for (const auto& flavor : aFlavorList) {
431           if (transferableFlavor.Equals(flavor)) {
432             MOZ_CLIPBOARD_LOG("    has %s", flavor.get());
433             return true;
434           }
435         }
436       }
437     }
439     if (MOZ_CLIPBOARD_LOG_ENABLED()) {
440       MOZ_CLIPBOARD_LOG("    no targets at clipboard (bad match)\n");
441     }
443     return false;
444   }
446   NSPasteboard* cocoaPasteboard = GetPasteboard(aWhichClipboard);
447   MOZ_ASSERT(cocoaPasteboard);
448   if (MOZ_CLIPBOARD_LOG_ENABLED()) {
449     NSArray* types = [cocoaPasteboard types];
450     uint32_t count = [types count];
451     MOZ_CLIPBOARD_LOG("    Pasteboard types (nums %d)\n", count);
452     for (uint32_t i = 0; i < count; i++) {
453       NSPasteboardType type = [types objectAtIndex:i];
454       if (!type) {
455         MOZ_CLIPBOARD_LOG("        failed to get MIME\n");
456         continue;
457       }
458       MOZ_CLIPBOARD_LOG("        MIME %s\n", [type UTF8String]);
459     }
460   }
462   for (auto& mimeType : aFlavorList) {
463     NSString* pboardType = nil;
464     if (nsClipboard::IsStringType(mimeType, &pboardType)) {
465       NSString* availableType = [cocoaPasteboard
466           availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
467       if (availableType && [availableType isEqualToString:pboardType]) {
468         MOZ_CLIPBOARD_LOG("    has %s\n", mimeType.get());
469         return true;
470       }
471     } else if (mimeType.EqualsLiteral(kCustomTypesMime)) {
472       NSString* availableType = [cocoaPasteboard
473           availableTypeFromArray:
474               [NSArray
475                   arrayWithObject:[UTIHelper stringFromPboardType:
476                                                  kMozCustomTypesPboardType]]];
477       if (availableType) {
478         MOZ_CLIPBOARD_LOG("    has %s\n", mimeType.get());
479         return true;
480       }
481     } else if (mimeType.EqualsLiteral(kJPEGImageMime) ||
482                mimeType.EqualsLiteral(kJPGImageMime) ||
483                mimeType.EqualsLiteral(kPNGImageMime) ||
484                mimeType.EqualsLiteral(kGIFImageMime)) {
485       NSString* availableType = [cocoaPasteboard
486           availableTypeFromArray:
487               [NSArray
488                   arrayWithObjects:
489                       [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF],
490                       [UTIHelper stringFromPboardType:NSPasteboardTypePNG],
491                       nil]];
492       if (availableType) {
493         MOZ_CLIPBOARD_LOG("    has %s\n", mimeType.get());
494         return true;
495       }
496     } else if (mimeType.EqualsLiteral(kFileMime)) {
497       NSArray* items = [cocoaPasteboard pasteboardItems];
498       if (items && [items count] > 0) {
499         // XXX we only check the first pasteboard item as we only get data from
500         // first item in TransferableFromPasteboard for now.
501         if (NSPasteboardItem* item = [items objectAtIndex:0]) {
502           if ([item availableTypeFromArray:
503                         [NSArray
504                             arrayWithObjects:[UTIHelper
505                                                  stringFromPboardType:
506                                                      (NSString*)kUTTypeFileURL],
507                                              nil]]) {
508             MOZ_CLIPBOARD_LOG("    has %s\n", mimeType.get());
509             return true;
510           }
511         }
512       }
513     }
514   }
516   if (MOZ_CLIPBOARD_LOG_ENABLED()) {
517     MOZ_CLIPBOARD_LOG("    no targets at clipboard (bad match)\n");
518   }
520   return false;
522   NS_OBJC_END_TRY_BLOCK_RETURN(mozilla::Err(NS_ERROR_FAILURE));
525 // static
526 mozilla::Maybe<uint32_t> nsClipboard::FindIndexOfImageFlavor(
527     const nsTArray<nsCString>& aMIMETypes) {
528   for (uint32_t i = 0; i < aMIMETypes.Length(); ++i) {
529     if (nsClipboard::IsImageType(aMIMETypes[i])) {
530       return mozilla::Some(i);
531     }
532   }
534   return mozilla::Nothing();
537 // This function converts anything that other applications might understand into
538 // the system format and puts it into a dictionary which it returns. static
539 NSDictionary* nsClipboard::PasteboardDictFromTransferable(
540     nsITransferable* aTransferable) {
541   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
543   if (!aTransferable) {
544     return nil;
545   }
547   NSMutableDictionary* pasteboardOutputDict = [NSMutableDictionary dictionary];
549   nsTArray<nsCString> flavors;
550   nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors);
551   if (NS_FAILED(rv)) {
552     return nil;
553   }
555   const mozilla::Maybe<uint32_t> imageFlavorIndex =
556       nsClipboard::FindIndexOfImageFlavor(flavors);
558   if (imageFlavorIndex) {
559     // When right-clicking and "Copy Image" is clicked on macOS, some apps
560     // expect the first flavor to be the image flavor. See bug 1689992. For
561     // other apps, the order shouldn't matter.
562     std::swap(*flavors.begin(), flavors[*imageFlavorIndex]);
563   }
564   for (uint32_t i = 0; i < flavors.Length(); i++) {
565     nsCString& flavorStr = flavors[i];
567     MOZ_CLIPBOARD_LOG("writing out clipboard data of type %s (%d)\n",
568                       flavorStr.get(), i);
570     NSString* pboardType = nil;
571     if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
572       nsCOMPtr<nsISupports> genericDataWrapper;
573       rv = aTransferable->GetTransferData(flavorStr.get(),
574                                           getter_AddRefs(genericDataWrapper));
575       if (NS_FAILED(rv)) {
576         continue;
577       }
579       nsAutoString data;
580       if (nsCOMPtr<nsISupportsString> text =
581               do_QueryInterface(genericDataWrapper)) {
582         text->GetData(data);
583       }
585       NSString* nativeString;
586       if (!data.IsEmpty()) {
587         nativeString = [NSString stringWithCharacters:(const unichar*)data.get()
588                                                length:data.Length()];
589       } else {
590         nativeString = [NSString string];
591       }
593       // be nice to Carbon apps, normalize the receiver's contents using Form C.
594       nativeString = [nativeString precomposedStringWithCanonicalMapping];
595       if (nativeString) {
596         [pasteboardOutputDict setObject:nativeString forKey:pboardType];
597       }
599       if (aTransferable->GetIsPrivateData()) {
600         // In the case of password strings, we want to include the key for
601         // concealed type. These will be flagged as private data.
602         [pasteboardOutputDict
603             setObject:nativeString
604                forKey:[UTIHelper
605                           stringFromPboardType:kPasteboardConcealedType]];
606       }
607     } else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
608       nsCOMPtr<nsISupports> genericDataWrapper;
609       rv = aTransferable->GetTransferData(flavorStr.get(),
610                                           getter_AddRefs(genericDataWrapper));
611       if (NS_FAILED(rv)) {
612         continue;
613       }
615       nsAutoCString data;
616       if (nsCOMPtr<nsISupportsCString> text =
617               do_QueryInterface(genericDataWrapper)) {
618         text->GetData(data);
619       }
621       if (!data.IsEmpty()) {
622         NSData* nativeData = [NSData dataWithBytes:data.get()
623                                             length:data.Length()];
624         NSString* customType =
625             [UTIHelper stringFromPboardType:kMozCustomTypesPboardType];
626         if (!nativeData) {
627           continue;
628         }
629         [pasteboardOutputDict setObject:nativeData forKey:customType];
630       }
631     } else if (nsClipboard::IsImageType(flavorStr)) {
632       nsCOMPtr<nsISupports> transferSupports;
633       rv = aTransferable->GetTransferData(flavorStr.get(),
634                                           getter_AddRefs(transferSupports));
635       if (NS_FAILED(rv)) {
636         continue;
637       }
639       nsCOMPtr<imgIContainer> image(do_QueryInterface(transferSupports));
640       if (!image) {
641         NS_WARNING("Image isn't an imgIContainer in transferable");
642         continue;
643       }
645       RefPtr<SourceSurface> surface = image->GetFrame(
646           imgIContainer::FRAME_CURRENT,
647           imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
648       if (!surface) {
649         continue;
650       }
651       CGImageRef imageRef = NULL;
652       rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef);
653       if (NS_FAILED(rv) || !imageRef) {
654         continue;
655       }
657       // Convert the CGImageRef to TIFF and PNG data.
658       CFMutableDataRef tiffData = CFDataCreateMutable(kCFAllocatorDefault, 0);
659       CFMutableDataRef pngData = CFDataCreateMutable(kCFAllocatorDefault, 0);
660       CGImageDestinationRef destRefTIFF = CGImageDestinationCreateWithData(
661           tiffData, CFSTR("public.tiff"), 1, NULL);
662       CGImageDestinationRef destRefPNG = CGImageDestinationCreateWithData(
663           pngData, CFSTR("public.png"), 1, NULL);
664       CGImageDestinationAddImage(destRefTIFF, imageRef, NULL);
665       CGImageDestinationAddImage(destRefPNG, imageRef, NULL);
666       const bool successfullyConvertedTIFF =
667           CGImageDestinationFinalize(destRefTIFF);
668       const bool successfullyConvertedPNG =
669           CGImageDestinationFinalize(destRefPNG);
671       CGImageRelease(imageRef);
672       if (destRefTIFF) {
673         CFRelease(destRefTIFF);
674       }
675       if (destRefPNG) {
676         CFRelease(destRefPNG);
677       }
679       if (successfullyConvertedTIFF && tiffData) {
680         NSString* tiffType =
681             [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF];
682         [pasteboardOutputDict setObject:(NSMutableData*)tiffData
683                                  forKey:tiffType];
684       }
685       if (successfullyConvertedPNG && pngData) {
686         NSString* pngType =
687             [UTIHelper stringFromPboardType:NSPasteboardTypePNG];
689         [pasteboardOutputDict setObject:(NSMutableData*)pngData forKey:pngType];
690       }
691       if (tiffData) {
692         CFRelease(tiffData);
693       }
694       if (pngData) {
695         CFRelease(pngData);
696       }
697     } else if (flavorStr.EqualsLiteral(kFileMime)) {
698       nsCOMPtr<nsISupports> genericFile;
699       rv = aTransferable->GetTransferData(flavorStr.get(),
700                                           getter_AddRefs(genericFile));
701       if (NS_FAILED(rv)) {
702         continue;
703       }
705       nsCOMPtr<nsIFile> file(do_QueryInterface(genericFile));
706       if (!file) {
707         continue;
708       }
710       nsAutoString fileURI;
711       rv = file->GetPath(fileURI);
712       if (NS_FAILED(rv)) {
713         continue;
714       }
716       NSString* str = nsCocoaUtils::ToNSString(fileURI);
717       NSURL* url = [NSURL fileURLWithPath:str isDirectory:NO];
718       if (!url || ![url absoluteString]) {
719         continue;
720       }
721       NSString* fileUTType =
722           [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL];
723       [pasteboardOutputDict setObject:[url absoluteString] forKey:fileUTType];
724     } else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
725       NSString* urlPromise = [UTIHelper
726           stringFromPboardType:(NSString*)kPasteboardTypeFileURLPromise];
727       NSString* urlPromiseContent = [UTIHelper
728           stringFromPboardType:(NSString*)kPasteboardTypeFilePromiseContent];
729       [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""]
730                                forKey:urlPromise];
731       [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""]
732                                forKey:urlPromiseContent];
733     } else if (flavorStr.EqualsLiteral(kURLMime)) {
734       nsCOMPtr<nsISupports> genericURL;
735       rv = aTransferable->GetTransferData(flavorStr.get(),
736                                           getter_AddRefs(genericURL));
737       nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
739       nsAutoString url;
740       urlObject->GetData(url);
742       NSString* nativeTitle = nil;
744       // A newline embedded in the URL means that the form is actually URL +
745       // title. This embedding occurs in nsDragService::GetData.
746       int32_t newlinePos = url.FindChar(char16_t('\n'));
747       if (newlinePos >= 0) {
748         url.Truncate(newlinePos);
750         nsAutoString urlTitle;
751         urlObject->GetData(urlTitle);
752         urlTitle.Mid(urlTitle, newlinePos + 1,
753                      urlTitle.Length() - (newlinePos + 1));
755         nativeTitle =
756             [NSString stringWithCharacters:reinterpret_cast<const unichar*>(
757                                                urlTitle.get())
758                                     length:urlTitle.Length()];
759       }
760       // The Finder doesn't like getting random binary data aka
761       // Unicode, so change it into an escaped URL containing only
762       // ASCII.
763       nsAutoCString utf8Data = NS_ConvertUTF16toUTF8(url.get(), url.Length());
764       nsAutoCString escData;
765       NS_EscapeURL(utf8Data.get(), utf8Data.Length(),
766                    esc_OnlyNonASCII | esc_AlwaysCopy, escData);
768       NSString* nativeURL = [NSString stringWithUTF8String:escData.get()];
769       NSString* publicUrl =
770           [UTIHelper stringFromPboardType:kPublicUrlPboardType];
771       if (!nativeURL) {
772         continue;
773       }
774       [pasteboardOutputDict setObject:nativeURL forKey:publicUrl];
775       if (nativeTitle) {
776         NSArray* urlsAndTitles = @[ @[ nativeURL ], @[ nativeTitle ] ];
777         NSString* urlName =
778             [UTIHelper stringFromPboardType:kPublicUrlNamePboardType];
779         NSString* urlsWithTitles =
780             [UTIHelper stringFromPboardType:kUrlsWithTitlesPboardType];
781         [pasteboardOutputDict setObject:nativeTitle forKey:urlName];
782         [pasteboardOutputDict setObject:urlsAndTitles forKey:urlsWithTitles];
783       }
784     }
785     // If it wasn't a type that we recognize as exportable we don't put it on
786     // the system clipboard. We'll just access it from our cached transferable
787     // when we need it.
788   }
790   return pasteboardOutputDict;
792   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
795 bool nsClipboard::IsStringType(const nsCString& aMIMEType,
796                                NSString** aPboardType) {
797   if (aMIMEType.EqualsLiteral(kTextMime)) {
798     *aPboardType = [UTIHelper stringFromPboardType:NSPasteboardTypeString];
799     return true;
800   } else if (aMIMEType.EqualsLiteral(kRTFMime)) {
801     *aPboardType = [UTIHelper stringFromPboardType:NSPasteboardTypeRTF];
802     return true;
803   } else if (aMIMEType.EqualsLiteral(kHTMLMime)) {
804     *aPboardType = [UTIHelper stringFromPboardType:NSPasteboardTypeHTML];
805     return true;
806   } else {
807     return false;
808   }
811 // static
812 bool nsClipboard::IsImageType(const nsACString& aMIMEType) {
813   return aMIMEType.EqualsLiteral(kPNGImageMime) ||
814          aMIMEType.EqualsLiteral(kJPEGImageMime) ||
815          aMIMEType.EqualsLiteral(kJPGImageMime) ||
816          aMIMEType.EqualsLiteral(kGIFImageMime) ||
817          aMIMEType.EqualsLiteral(kNativeImageMime);
820 NSString* nsClipboard::WrapHtmlForSystemPasteboard(NSString* aString) {
821   NSString* wrapped =
822       [NSString stringWithFormat:@"<html>"
823                                   "<head>"
824                                   "<meta http-equiv=\"content-type\" "
825                                   "content=\"text/html; charset=utf-8\">"
826                                   "</head>"
827                                   "<body>"
828                                   "%@"
829                                   "</body>"
830                                   "</html>",
831                                  aString];
832   return wrapped;
835 nsresult nsClipboard::EmptyNativeClipboardData(int32_t aWhichClipboard) {
836   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
838   MOZ_DIAGNOSTIC_ASSERT(
839       nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
841   if (kSelectionCache == aWhichClipboard) {
842     ClearSelectionCache();
843     return NS_OK;
844   }
846   if (NSPasteboard* cocoaPasteboard = GetPasteboard(aWhichClipboard)) {
847     [cocoaPasteboard clearContents];
848   }
850   return NS_OK;
852   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
855 mozilla::Result<int32_t, nsresult>
856 nsClipboard::GetNativeClipboardSequenceNumber(int32_t aWhichClipboard) {
857   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
859   MOZ_DIAGNOSTIC_ASSERT(
860       nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
862   if (kSelectionCache == aWhichClipboard) {
863     return sSelectionCacheChangeCount;
864   }
866   NSPasteboard* cocoaPasteboard = GetPasteboard(aWhichClipboard);
867   if (!cocoaPasteboard) {
868     return mozilla::Err(NS_ERROR_FAILURE);
869   }
871   return [cocoaPasteboard changeCount];
873   NS_OBJC_END_TRY_BLOCK_RETURN(mozilla::Err(NS_ERROR_FAILURE));