/* -*- 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 #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 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 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 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 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 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 flavors; nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors); if (NS_FAILED(rv)) { return NS_ERROR_FAILURE; } for (const auto& flavor : flavors) { nsCOMPtr 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 nsClipboard::HasNativeClipboardDataMatchingFlavors( const nsTArray& aFlavorList, int32_t aWhichClipboard) { NS_OBJC_BEGIN_TRY_BLOCK_RETURN; MOZ_DIAGNOSTIC_ASSERT( nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)); if (kSelectionCache == aWhichClipboard) { nsTArray 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 nsClipboard::FindIndexOfImageFlavor( const nsTArray& 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 flavors; nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors); if (NS_FAILED(rv)) { return nil; } const mozilla::Maybe 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 genericDataWrapper; rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericDataWrapper)); if (NS_FAILED(rv)) { continue; } nsAutoString data; if (nsCOMPtr 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 genericDataWrapper; rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericDataWrapper)); if (NS_FAILED(rv)) { continue; } nsAutoCString data; if (nsCOMPtr 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 transferSupports; rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(transferSupports)); if (NS_FAILED(rv)) { continue; } nsCOMPtr image(do_QueryInterface(transferSupports)); if (!image) { NS_WARNING("Image isn't an imgIContainer in transferable"); continue; } RefPtr 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 genericFile; rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericFile)); if (NS_FAILED(rv)) { continue; } nsCOMPtr 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 genericURL; rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(genericURL)); nsCOMPtr 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( 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:@"" "" "" "" "" "%@" "" "", 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 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)); }