diff options
Diffstat (limited to '')
-rw-r--r-- | widget/cocoa/nsClipboard.mm | 875 |
1 files changed, 875 insertions, 0 deletions
diff --git a/widget/cocoa/nsClipboard.mm b/widget/cocoa/nsClipboard.mm new file mode 100644 index 0000000000..ba747e5b27 --- /dev/null +++ b/widget/cocoa/nsClipboard.mm @@ -0,0 +1,875 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <algorithm> + +#include "mozilla/gfx/2D.h" +#include "mozilla/Logging.h" +#include "mozilla/Unused.h" + +#include "gfxPlatform.h" +#include "nsArrayUtils.h" +#include "nsCOMPtr.h" +#include "nsClipboard.h" +#include "nsString.h" +#include "nsISupportsPrimitives.h" +#include "nsPrimitiveHelpers.h" +#include "nsIFile.h" +#include "nsStringStream.h" +#include "nsEscape.h" +#include "nsPrintfCString.h" +#include "nsObjCExceptions.h" +#include "imgIContainer.h" +#include "nsCocoaUtils.h" + +using mozilla::gfx::DataSourceSurface; +using mozilla::gfx::SourceSurface; + +mozilla::StaticRefPtr<nsITransferable> nsClipboard::sSelectionCache; +int32_t nsClipboard::sSelectionCacheChangeCount = 0; + +nsClipboard::nsClipboard() + : nsBaseClipboard(mozilla::dom::ClipboardCapabilities( + false /* supportsSelectionClipboard */, + true /* supportsFindClipboard */, + true /* supportsSelectionCache */)) {} + +nsClipboard::~nsClipboard() { ClearSelectionCache(); } + +NS_IMPL_ISUPPORTS_INHERITED0(nsClipboard, nsBaseClipboard) + +namespace { + +// We separate this into its own function because after an @try, all local +// variables within that function get marked as volatile, and our C++ type +// system doesn't like volatile things. +static NSData* GetDataFromPasteboard(NSPasteboard* aPasteboard, + NSString* aType) { + NSData* data = nil; + @try { + data = [aPasteboard dataForType:aType]; + } @catch (NSException* e) { + NS_WARNING(nsPrintfCString("Exception raised while getting data from the " + "pasteboard: \"%s - %s\"", + [[e name] UTF8String], [[e reason] UTF8String]) + .get()); + mozilla::Unused << e; + } + return data; +} + +static NSPasteboard* GetPasteboard(int32_t aWhichClipboard) { + switch (aWhichClipboard) { + case nsIClipboard::kGlobalClipboard: + return [NSPasteboard generalPasteboard]; + case nsIClipboard::kFindClipboard: + return [NSPasteboard pasteboardWithName:NSPasteboardNameFind]; + default: + return nil; + } +} + +} // namespace + +void nsClipboard::SetSelectionCache(nsITransferable* aTransferable) { + sSelectionCacheChangeCount++; + sSelectionCache = aTransferable; +} + +void nsClipboard::ClearSelectionCache() { SetSelectionCache(nullptr); } + +NS_IMETHODIMP +nsClipboard::SetNativeClipboardData(nsITransferable* aTransferable, + int32_t aWhichClipboard) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + MOZ_ASSERT(aTransferable); + MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)); + + if (aWhichClipboard == kSelectionCache) { + SetSelectionCache(aTransferable); + return NS_OK; + } + + NSDictionary* pasteboardOutputDict = + PasteboardDictFromTransferable(aTransferable); + if (!pasteboardOutputDict) return NS_ERROR_FAILURE; + + unsigned int outputCount = [pasteboardOutputDict count]; + NSArray* outputKeys = [pasteboardOutputDict allKeys]; + NSPasteboard* cocoaPasteboard = GetPasteboard(aWhichClipboard); + MOZ_ASSERT(cocoaPasteboard); + if (aWhichClipboard == kFindClipboard) { + NSString* stringType = + [UTIHelper stringFromPboardType:NSPasteboardTypeString]; + [cocoaPasteboard declareTypes:[NSArray arrayWithObject:stringType] + owner:nil]; + } else { + // Write everything else out to the general pasteboard. + MOZ_ASSERT(aWhichClipboard == kGlobalClipboard); + [cocoaPasteboard declareTypes:outputKeys owner:nil]; + } + + for (unsigned int i = 0; i < outputCount; i++) { + NSString* currentKey = [outputKeys objectAtIndex:i]; + id currentValue = [pasteboardOutputDict valueForKey:currentKey]; + if (aWhichClipboard == kFindClipboard) { + if ([currentKey isEqualToString:[UTIHelper stringFromPboardType: + NSPasteboardTypeString]]) { + [cocoaPasteboard setString:currentValue forType:currentKey]; + } + } else { + if ([currentKey isEqualToString:[UTIHelper stringFromPboardType: + NSPasteboardTypeString]] || + [currentKey + isEqualToString:[UTIHelper + stringFromPboardType:kPublicUrlPboardType]] || + [currentKey + isEqualToString: + [UTIHelper stringFromPboardType:kPublicUrlNamePboardType]]) { + [cocoaPasteboard setString:currentValue forType:currentKey]; + } else if ([currentKey + isEqualToString: + [UTIHelper + stringFromPboardType:kUrlsWithTitlesPboardType]]) { + [cocoaPasteboard + setPropertyList:[pasteboardOutputDict valueForKey:currentKey] + forType:currentKey]; + } else if ([currentKey + isEqualToString:[UTIHelper stringFromPboardType: + NSPasteboardTypeHTML]]) { + [cocoaPasteboard + setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue)) + forType:currentKey]; + } else if ([currentKey + isEqualToString:[UTIHelper stringFromPboardType: + kMozFileUrlsPboardType]]) { + [cocoaPasteboard writeObjects:currentValue]; + } else if ([currentKey + isEqualToString: + [UTIHelper + stringFromPboardType:(NSString*)kUTTypeFileURL]]) { + [cocoaPasteboard setString:currentValue forType:currentKey]; + } else if ([currentKey + isEqualToString: + [UTIHelper + stringFromPboardType:kPasteboardConcealedType]]) { + // It's fine to set the data to null for this field - this field is an + // addition to a value's other type and works like a flag. + [cocoaPasteboard setData:NULL forType:currentKey]; + } else { + [cocoaPasteboard setData:currentValue forType:currentKey]; + } + } + } + + return NS_OK; + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +nsresult nsClipboard::TransferableFromPasteboard( + nsITransferable* aTransferable, NSPasteboard* cocoaPasteboard) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + // get flavor list that includes all acceptable flavors (including ones + // obtained through conversion) + nsTArray<nsCString> flavors; + nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + + for (uint32_t i = 0; i < flavors.Length(); i++) { + nsCString& flavorStr = flavors[i]; + + // printf("looking for clipboard data of type %s\n", flavorStr.get()); + + NSString* pboardType = nil; + if (nsClipboard::IsStringType(flavorStr, &pboardType)) { + NSString* pString = [cocoaPasteboard stringForType:pboardType]; + if (!pString) { + continue; + } + + NSData* stringData; + bool isRTF = [pboardType + isEqualToString:[UTIHelper stringFromPboardType:NSPasteboardTypeRTF]]; + if (isRTF) { + stringData = [pString dataUsingEncoding:NSASCIIStringEncoding]; + } else { + stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding]; + } + unsigned int dataLength = [stringData length]; + void* clipboardDataPtr = malloc(dataLength); + if (!clipboardDataPtr) { + return NS_ERROR_OUT_OF_MEMORY; + } + [stringData getBytes:clipboardDataPtr length:dataLength]; + + // The DOM only wants LF, so convert from MacOS line endings to DOM line + // endings. + int32_t signedDataLength = dataLength; + nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks( + isRTF, &clipboardDataPtr, &signedDataLength); + dataLength = signedDataLength; + + // skip BOM (Byte Order Mark to distinguish little or big endian) + char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr; + if ((dataLength > 2) && ((clipboardDataPtrNoBOM[0] == 0xFEFF) || + (clipboardDataPtrNoBOM[0] == 0xFFFE))) { + dataLength -= sizeof(char16_t); + clipboardDataPtrNoBOM += 1; + } + + nsCOMPtr<nsISupports> genericDataWrapper; + nsPrimitiveHelpers::CreatePrimitiveForData( + flavorStr, clipboardDataPtrNoBOM, dataLength, + getter_AddRefs(genericDataWrapper)); + aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper); + free(clipboardDataPtr); + break; + } else if (flavorStr.EqualsLiteral(kFileMime)) { + NSArray* items = [cocoaPasteboard pasteboardItems]; + if (!items || [items count] <= 0) { + continue; + } + + // XXX we don't support multiple clipboard item on DOM and XPCOM interface + // for now, so we only get the data from the first pasteboard item. + NSPasteboardItem* item = [items objectAtIndex:0]; + if (!item) { + continue; + } + + nsCocoaUtils::SetTransferDataForTypeFromPasteboardItem(aTransferable, + flavorStr, item); + } else if (flavorStr.EqualsLiteral(kCustomTypesMime)) { + NSString* type = [cocoaPasteboard + availableTypeFromArray: + [NSArray + arrayWithObject:[UTIHelper stringFromPboardType: + kMozCustomTypesPboardType]]]; + if (!type) { + continue; + } + + NSData* pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type); + if (!pasteboardData) { + continue; + } + + unsigned int dataLength = [pasteboardData length]; + void* clipboardDataPtr = malloc(dataLength); + if (!clipboardDataPtr) { + return NS_ERROR_OUT_OF_MEMORY; + } + [pasteboardData getBytes:clipboardDataPtr length:dataLength]; + + nsCOMPtr<nsISupports> genericDataWrapper; + nsPrimitiveHelpers::CreatePrimitiveForData( + flavorStr, clipboardDataPtr, dataLength, + getter_AddRefs(genericDataWrapper)); + + aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper); + free(clipboardDataPtr); + } else if (flavorStr.EqualsLiteral(kJPEGImageMime) || + flavorStr.EqualsLiteral(kJPGImageMime) || + flavorStr.EqualsLiteral(kPNGImageMime) || + flavorStr.EqualsLiteral(kGIFImageMime)) { + // Figure out if there's data on the pasteboard we can grab (sanity check) + NSString* type = [cocoaPasteboard + availableTypeFromArray: + [NSArray + arrayWithObjects: + [UTIHelper + stringFromPboardType:(NSString*)kUTTypeFileURL], + [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF], + [UTIHelper stringFromPboardType:NSPasteboardTypePNG], + nil]]; + if (!type) continue; + + // Read data off the clipboard + NSData* pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type); + if (!pasteboardData) continue; + + // Figure out what type we're converting to + CFStringRef outputType = NULL; + if (flavorStr.EqualsLiteral(kJPEGImageMime) || + flavorStr.EqualsLiteral(kJPGImageMime)) + outputType = CFSTR("public.jpeg"); + else if (flavorStr.EqualsLiteral(kPNGImageMime)) + outputType = CFSTR("public.png"); + else if (flavorStr.EqualsLiteral(kGIFImageMime)) + outputType = CFSTR("com.compuserve.gif"); + else + continue; + + // Use ImageIO to interpret the data on the clipboard and transcode. + // Note that ImageIO, like all CF APIs, allows NULLs to propagate freely + // and safely in most cases (like ObjC). A notable exception is CFRelease. + NSDictionary* options = [NSDictionary + dictionaryWithObjectsAndKeys:(NSNumber*)kCFBooleanTrue, + kCGImageSourceShouldAllowFloat, type, + kCGImageSourceTypeIdentifierHint, nil]; + CGImageSourceRef source = nullptr; + if (type == [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL]) { + NSString* urlStr = [cocoaPasteboard stringForType:type]; + NSURL* url = [NSURL URLWithString:urlStr]; + source = + CGImageSourceCreateWithURL((CFURLRef)url, (CFDictionaryRef)options); + } else { + source = CGImageSourceCreateWithData((CFDataRef)pasteboardData, + (CFDictionaryRef)options); + } + + NSMutableData* encodedData = [NSMutableData data]; + CGImageDestinationRef dest = CGImageDestinationCreateWithData( + (CFMutableDataRef)encodedData, outputType, 1, NULL); + CGImageDestinationAddImageFromSource(dest, source, 0, NULL); + bool successfullyConverted = CGImageDestinationFinalize(dest); + + if (successfullyConverted) { + // Put the converted data in a form Gecko can understand + nsCOMPtr<nsIInputStream> byteStream; + NS_NewByteInputStream(getter_AddRefs(byteStream), + mozilla::Span((const char*)[encodedData bytes], + [encodedData length]), + NS_ASSIGNMENT_COPY); + + aTransferable->SetTransferData(flavorStr.get(), byteStream); + } + + if (dest) CFRelease(dest); + if (source) CFRelease(source); + + if (successfullyConverted) { + // XXX Maybe try to fill in more types? Is there a point? + break; + } else { + continue; + } + } + } + + return NS_OK; + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +NS_IMETHODIMP +nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, + int32_t aWhichClipboard) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + MOZ_DIAGNOSTIC_ASSERT(aTransferable); + MOZ_DIAGNOSTIC_ASSERT( + nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)); + + if (kSelectionCache == aWhichClipboard) { + if (!sSelectionCache) { + return NS_OK; + } + + // get flavor list that includes all acceptable flavors (including ones + // obtained through conversion) + nsTArray<nsCString> flavors; + nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + for (const auto& flavor : flavors) { + nsCOMPtr<nsISupports> dataSupports; + rv = sSelectionCache->GetTransferData(flavor.get(), + getter_AddRefs(dataSupports)); + if (NS_SUCCEEDED(rv)) { + MOZ_CLIPBOARD_LOG("%s: getting %s from cache.", __FUNCTION__, + flavor.get()); + aTransferable->SetTransferData(flavor.get(), dataSupports); + // XXX Maybe try to fill in more types? Is there a point? + break; + } + } + return NS_OK; + } + + NSPasteboard* cocoaPasteboard = GetPasteboard(aWhichClipboard); + if (!cocoaPasteboard) { + return NS_ERROR_FAILURE; + } + + return TransferableFromPasteboard(aTransferable, cocoaPasteboard); + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +// returns true if we have *any* of the passed in flavors available for pasting +mozilla::Result<bool, nsresult> +nsClipboard::HasNativeClipboardDataMatchingFlavors( + const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + MOZ_DIAGNOSTIC_ASSERT( + nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)); + + if (kSelectionCache == aWhichClipboard) { + nsTArray<nsCString> transferableFlavors; + if (sSelectionCache && + NS_SUCCEEDED(sSelectionCache->FlavorsTransferableCanExport( + transferableFlavors))) { + if (MOZ_CLIPBOARD_LOG_ENABLED()) { + MOZ_CLIPBOARD_LOG(" SelectionCache types (nums %zu)\n", + transferableFlavors.Length()); + for (const auto& transferableFlavor : transferableFlavors) { + MOZ_CLIPBOARD_LOG(" MIME %s", transferableFlavor.get()); + } + } + + for (const auto& transferableFlavor : transferableFlavors) { + for (const auto& flavor : aFlavorList) { + if (transferableFlavor.Equals(flavor)) { + MOZ_CLIPBOARD_LOG(" has %s", flavor.get()); + return true; + } + } + } + } + + if (MOZ_CLIPBOARD_LOG_ENABLED()) { + MOZ_CLIPBOARD_LOG(" no targets at clipboard (bad match)\n"); + } + + return false; + } + + NSPasteboard* cocoaPasteboard = GetPasteboard(aWhichClipboard); + MOZ_ASSERT(cocoaPasteboard); + if (MOZ_CLIPBOARD_LOG_ENABLED()) { + NSArray* types = [cocoaPasteboard types]; + uint32_t count = [types count]; + MOZ_CLIPBOARD_LOG(" Pasteboard types (nums %d)\n", count); + for (uint32_t i = 0; i < count; i++) { + NSPasteboardType type = [types objectAtIndex:i]; + if (!type) { + MOZ_CLIPBOARD_LOG(" failed to get MIME\n"); + continue; + } + MOZ_CLIPBOARD_LOG(" MIME %s\n", [type UTF8String]); + } + } + + for (auto& mimeType : aFlavorList) { + NSString* pboardType = nil; + if (nsClipboard::IsStringType(mimeType, &pboardType)) { + NSString* availableType = [cocoaPasteboard + availableTypeFromArray:[NSArray arrayWithObject:pboardType]]; + if (availableType && [availableType isEqualToString:pboardType]) { + MOZ_CLIPBOARD_LOG(" has %s\n", mimeType.get()); + return true; + } + } else if (mimeType.EqualsLiteral(kCustomTypesMime)) { + NSString* availableType = [cocoaPasteboard + availableTypeFromArray: + [NSArray + arrayWithObject:[UTIHelper stringFromPboardType: + kMozCustomTypesPboardType]]]; + if (availableType) { + MOZ_CLIPBOARD_LOG(" has %s\n", mimeType.get()); + return true; + } + } else if (mimeType.EqualsLiteral(kJPEGImageMime) || + mimeType.EqualsLiteral(kJPGImageMime) || + mimeType.EqualsLiteral(kPNGImageMime) || + mimeType.EqualsLiteral(kGIFImageMime)) { + NSString* availableType = [cocoaPasteboard + availableTypeFromArray: + [NSArray + arrayWithObjects: + [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF], + [UTIHelper stringFromPboardType:NSPasteboardTypePNG], + nil]]; + if (availableType) { + MOZ_CLIPBOARD_LOG(" has %s\n", mimeType.get()); + return true; + } + } else if (mimeType.EqualsLiteral(kFileMime)) { + NSArray* items = [cocoaPasteboard pasteboardItems]; + if (items && [items count] > 0) { + // XXX we only check the first pasteboard item as we only get data from + // first item in TransferableFromPasteboard for now. + if (NSPasteboardItem* item = [items objectAtIndex:0]) { + if (NSString *availableType = [item + availableTypeFromArray: + [NSArray + arrayWithObjects:[UTIHelper + stringFromPboardType: + (NSString*)kUTTypeFileURL], + nil]]) { + MOZ_CLIPBOARD_LOG(" has %s\n", mimeType.get()); + return true; + } + } + } + } + } + + if (MOZ_CLIPBOARD_LOG_ENABLED()) { + MOZ_CLIPBOARD_LOG(" no targets at clipboard (bad match)\n"); + } + + return false; + + NS_OBJC_END_TRY_BLOCK_RETURN(mozilla::Err(NS_ERROR_FAILURE)); +} + +// static +mozilla::Maybe<uint32_t> nsClipboard::FindIndexOfImageFlavor( + const nsTArray<nsCString>& aMIMETypes) { + for (uint32_t i = 0; i < aMIMETypes.Length(); ++i) { + if (nsClipboard::IsImageType(aMIMETypes[i])) { + return mozilla::Some(i); + } + } + + return mozilla::Nothing(); +} + +// This function converts anything that other applications might understand into +// the system format and puts it into a dictionary which it returns. static +NSDictionary* nsClipboard::PasteboardDictFromTransferable( + nsITransferable* aTransferable) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + if (!aTransferable) { + return nil; + } + + NSMutableDictionary* pasteboardOutputDict = [NSMutableDictionary dictionary]; + + nsTArray<nsCString> flavors; + nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors); + if (NS_FAILED(rv)) { + return nil; + } + + const mozilla::Maybe<uint32_t> imageFlavorIndex = + nsClipboard::FindIndexOfImageFlavor(flavors); + + if (imageFlavorIndex) { + // When right-clicking and "Copy Image" is clicked on macOS, some apps + // expect the first flavor to be the image flavor. See bug 1689992. For + // other apps, the order shouldn't matter. + std::swap(*flavors.begin(), flavors[*imageFlavorIndex]); + } + for (uint32_t i = 0; i < flavors.Length(); i++) { + nsCString& flavorStr = flavors[i]; + + MOZ_CLIPBOARD_LOG("writing out clipboard data of type %s (%d)\n", + flavorStr.get(), i); + + NSString* pboardType = nil; + if (nsClipboard::IsStringType(flavorStr, &pboardType)) { + nsCOMPtr<nsISupports> genericDataWrapper; + rv = aTransferable->GetTransferData(flavorStr.get(), + getter_AddRefs(genericDataWrapper)); + if (NS_FAILED(rv)) { + continue; + } + + nsAutoString data; + if (nsCOMPtr<nsISupportsString> text = + do_QueryInterface(genericDataWrapper)) { + text->GetData(data); + } + + NSString* nativeString; + if (!data.IsEmpty()) { + nativeString = [NSString stringWithCharacters:(const unichar*)data.get() + length:data.Length()]; + } else { + nativeString = [NSString string]; + } + + // be nice to Carbon apps, normalize the receiver's contents using Form C. + nativeString = [nativeString precomposedStringWithCanonicalMapping]; + if (nativeString) { + [pasteboardOutputDict setObject:nativeString forKey:pboardType]; + } + + if (aTransferable->GetIsPrivateData()) { + // In the case of password strings, we want to include the key for + // concealed type. These will be flagged as private data. + [pasteboardOutputDict + setObject:nativeString + forKey:[UTIHelper + stringFromPboardType:kPasteboardConcealedType]]; + } + } else if (flavorStr.EqualsLiteral(kCustomTypesMime)) { + nsCOMPtr<nsISupports> genericDataWrapper; + rv = aTransferable->GetTransferData(flavorStr.get(), + getter_AddRefs(genericDataWrapper)); + if (NS_FAILED(rv)) { + continue; + } + + nsAutoCString data; + if (nsCOMPtr<nsISupportsCString> text = + do_QueryInterface(genericDataWrapper)) { + text->GetData(data); + } + + if (!data.IsEmpty()) { + NSData* nativeData = [NSData dataWithBytes:data.get() + length:data.Length()]; + NSString* customType = + [UTIHelper stringFromPboardType:kMozCustomTypesPboardType]; + if (!nativeData) { + continue; + } + [pasteboardOutputDict setObject:nativeData forKey:customType]; + } + } else if (nsClipboard::IsImageType(flavorStr)) { + nsCOMPtr<nsISupports> transferSupports; + rv = aTransferable->GetTransferData(flavorStr.get(), + getter_AddRefs(transferSupports)); + if (NS_FAILED(rv)) { + continue; + } + + nsCOMPtr<imgIContainer> image(do_QueryInterface(transferSupports)); + if (!image) { + NS_WARNING("Image isn't an imgIContainer in transferable"); + continue; + } + + RefPtr<SourceSurface> surface = image->GetFrame( + imgIContainer::FRAME_CURRENT, + imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY); + if (!surface) { + continue; + } + CGImageRef imageRef = NULL; + rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef); + if (NS_FAILED(rv) || !imageRef) { + continue; + } + + // Convert the CGImageRef to TIFF and PNG data. + CFMutableDataRef tiffData = CFDataCreateMutable(kCFAllocatorDefault, 0); + CFMutableDataRef pngData = CFDataCreateMutable(kCFAllocatorDefault, 0); + CGImageDestinationRef destRefTIFF = CGImageDestinationCreateWithData( + tiffData, CFSTR("public.tiff"), 1, NULL); + CGImageDestinationRef destRefPNG = CGImageDestinationCreateWithData( + pngData, CFSTR("public.png"), 1, NULL); + CGImageDestinationAddImage(destRefTIFF, imageRef, NULL); + CGImageDestinationAddImage(destRefPNG, imageRef, NULL); + const bool successfullyConvertedTIFF = + CGImageDestinationFinalize(destRefTIFF); + const bool successfullyConvertedPNG = + CGImageDestinationFinalize(destRefPNG); + + CGImageRelease(imageRef); + if (destRefTIFF) { + CFRelease(destRefTIFF); + } + if (destRefPNG) { + CFRelease(destRefPNG); + } + + if (successfullyConvertedTIFF && tiffData) { + NSString* tiffType = + [UTIHelper stringFromPboardType:NSPasteboardTypeTIFF]; + [pasteboardOutputDict setObject:(NSMutableData*)tiffData + forKey:tiffType]; + } + if (successfullyConvertedPNG && pngData) { + NSString* pngType = + [UTIHelper stringFromPboardType:NSPasteboardTypePNG]; + + [pasteboardOutputDict setObject:(NSMutableData*)pngData forKey:pngType]; + } + if (tiffData) { + CFRelease(tiffData); + } + if (pngData) { + CFRelease(pngData); + } + } else if (flavorStr.EqualsLiteral(kFileMime)) { + nsCOMPtr<nsISupports> genericFile; + rv = aTransferable->GetTransferData(flavorStr.get(), + getter_AddRefs(genericFile)); + if (NS_FAILED(rv)) { + continue; + } + + nsCOMPtr<nsIFile> file(do_QueryInterface(genericFile)); + if (!file) { + continue; + } + + nsAutoString fileURI; + rv = file->GetPath(fileURI); + if (NS_FAILED(rv)) { + continue; + } + + NSString* str = nsCocoaUtils::ToNSString(fileURI); + NSURL* url = [NSURL fileURLWithPath:str isDirectory:NO]; + if (!url || ![url absoluteString]) { + continue; + } + NSString* fileUTType = + [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL]; + [pasteboardOutputDict setObject:[url absoluteString] forKey:fileUTType]; + } else if (flavorStr.EqualsLiteral(kFilePromiseMime)) { + NSString* urlPromise = [UTIHelper + stringFromPboardType:(NSString*)kPasteboardTypeFileURLPromise]; + NSString* urlPromiseContent = [UTIHelper + stringFromPboardType:(NSString*)kPasteboardTypeFilePromiseContent]; + [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""] + forKey:urlPromise]; + [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""] + forKey:urlPromiseContent]; + } else if (flavorStr.EqualsLiteral(kURLMime)) { + nsCOMPtr<nsISupports> genericURL; + rv = aTransferable->GetTransferData(flavorStr.get(), + getter_AddRefs(genericURL)); + nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL)); + + nsAutoString url; + urlObject->GetData(url); + + NSString* nativeTitle = nil; + + // A newline embedded in the URL means that the form is actually URL + + // title. This embedding occurs in nsDragService::GetData. + int32_t newlinePos = url.FindChar(char16_t('\n')); + if (newlinePos >= 0) { + url.Truncate(newlinePos); + + nsAutoString urlTitle; + urlObject->GetData(urlTitle); + urlTitle.Mid(urlTitle, newlinePos + 1, + urlTitle.Length() - (newlinePos + 1)); + + nativeTitle = + [NSString stringWithCharacters:reinterpret_cast<const unichar*>( + urlTitle.get()) + length:urlTitle.Length()]; + } + // The Finder doesn't like getting random binary data aka + // Unicode, so change it into an escaped URL containing only + // ASCII. + nsAutoCString utf8Data = NS_ConvertUTF16toUTF8(url.get(), url.Length()); + nsAutoCString escData; + NS_EscapeURL(utf8Data.get(), utf8Data.Length(), + esc_OnlyNonASCII | esc_AlwaysCopy, escData); + + NSString* nativeURL = [NSString stringWithUTF8String:escData.get()]; + NSString* publicUrl = + [UTIHelper stringFromPboardType:kPublicUrlPboardType]; + if (!nativeURL) { + continue; + } + [pasteboardOutputDict setObject:nativeURL forKey:publicUrl]; + if (nativeTitle) { + NSArray* urlsAndTitles = @[ @[ nativeURL ], @[ nativeTitle ] ]; + NSString* urlName = + [UTIHelper stringFromPboardType:kPublicUrlNamePboardType]; + NSString* urlsWithTitles = + [UTIHelper stringFromPboardType:kUrlsWithTitlesPboardType]; + [pasteboardOutputDict setObject:nativeTitle forKey:urlName]; + [pasteboardOutputDict setObject:urlsAndTitles forKey:urlsWithTitles]; + } + } + // If it wasn't a type that we recognize as exportable we don't put it on + // the system clipboard. We'll just access it from our cached transferable + // when we need it. + } + + return pasteboardOutputDict; + + NS_OBJC_END_TRY_BLOCK_RETURN(nil); +} + +bool nsClipboard::IsStringType(const nsCString& aMIMEType, + NSString** aPboardType) { + if (aMIMEType.EqualsLiteral(kTextMime)) { + *aPboardType = [UTIHelper stringFromPboardType:NSPasteboardTypeString]; + return true; + } else if (aMIMEType.EqualsLiteral(kRTFMime)) { + *aPboardType = [UTIHelper stringFromPboardType:NSPasteboardTypeRTF]; + return true; + } else if (aMIMEType.EqualsLiteral(kHTMLMime)) { + *aPboardType = [UTIHelper stringFromPboardType:NSPasteboardTypeHTML]; + return true; + } else { + return false; + } +} + +// static +bool nsClipboard::IsImageType(const nsACString& aMIMEType) { + return aMIMEType.EqualsLiteral(kPNGImageMime) || + aMIMEType.EqualsLiteral(kJPEGImageMime) || + aMIMEType.EqualsLiteral(kJPGImageMime) || + aMIMEType.EqualsLiteral(kGIFImageMime) || + aMIMEType.EqualsLiteral(kNativeImageMime); +} + +NSString* nsClipboard::WrapHtmlForSystemPasteboard(NSString* aString) { + NSString* wrapped = + [NSString stringWithFormat:@"<html>" + "<head>" + "<meta http-equiv=\"content-type\" " + "content=\"text/html; charset=utf-8\">" + "</head>" + "<body>" + "%@" + "</body>" + "</html>", + aString]; + return wrapped; +} + +nsresult nsClipboard::EmptyNativeClipboardData(int32_t aWhichClipboard) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + MOZ_DIAGNOSTIC_ASSERT( + nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)); + + if (kSelectionCache == aWhichClipboard) { + ClearSelectionCache(); + return NS_OK; + } + + if (NSPasteboard* cocoaPasteboard = GetPasteboard(aWhichClipboard)) { + [cocoaPasteboard clearContents]; + } + + return NS_OK; + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +mozilla::Result<int32_t, nsresult> +nsClipboard::GetNativeClipboardSequenceNumber(int32_t aWhichClipboard) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + MOZ_DIAGNOSTIC_ASSERT( + nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)); + + if (kSelectionCache == aWhichClipboard) { + return sSelectionCacheChangeCount; + } + + NSPasteboard* cocoaPasteboard = GetPasteboard(aWhichClipboard); + if (!cocoaPasteboard) { + return mozilla::Err(NS_ERROR_FAILURE); + } + + return [cocoaPasteboard changeCount]; + + NS_OBJC_END_TRY_BLOCK_RETURN(mozilla::Err(NS_ERROR_FAILURE)); +} |