Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / widget / src / cocoa / nsClipboard.mm
blobb800fa6ef8d5ae7d38b92d39e764c392d3de35ee
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is mozilla.org code.
16  *
17  * The Initial Developer of the Original Code is
18  * Mozilla Corporation.
19  * Portions created by the Initial Developer are Copyright (C) 2006
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  *    Josh Aas <josh@mozilla.com>
24  *
25  * Alternatively, the contents of this file may be used under the terms of
26  * either the GNU General Public License Version 2 or later (the "GPL"), or
27  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28  * in which case the provisions of the GPL or the LGPL are applicable instead
29  * of those above. If you wish to allow use of your version of this file only
30  * under the terms of either the GPL or the LGPL, and not to allow others to
31  * use your version of this file under the terms of the MPL, indicate your
32  * decision by deleting the provisions above and replace them with the notice
33  * and other provisions required by the GPL or the LGPL. If you do not delete
34  * the provisions above, a recipient may use your version of this file under
35  * the terms of any one of the MPL, the GPL or the LGPL.
36  *
37  * ***** END LICENSE BLOCK ***** */
39 #include "nsCOMPtr.h"
40 #include "nsClipboard.h"
41 #include "nsString.h"
42 #include "nsISupportsPrimitives.h"
43 #include "nsXPIDLString.h"
44 #include "nsPrimitiveHelpers.h"
45 #include "nsMemory.h"
46 #include "nsIImage.h"
47 #include "nsILocalFile.h"
48 #include "nsStringStream.h"
49 #include "nsDragService.h"
50 #include "nsEscape.h"
51 #include "nsPrintfCString.h"
52 #include "nsObjCExceptions.h"
54 // Screenshots use the (undocumented) png pasteboard type.
55 #define IMAGE_PASTEBOARD_TYPES NSTIFFPboardType, @"Apple PNG pasteboard type", nil
57 #ifdef MOZ_LOGGING
58 #define FORCE_PR_LOG
59 #endif
60 #include "prlog.h"
62 #ifdef PR_LOGGING
63 extern PRLogModuleInfo* sCocoaLog;
64 #endif
67 nsClipboard::nsClipboard() : nsBaseClipboard()
69   mChangeCount = 0;
73 nsClipboard::~nsClipboard()
78 // We separate this into its own function because after an @try, all local
79 // variables within that function get marked as volatile, and our C++ type 
80 // system doesn't like volatile things.
81 static NSData* 
82 GetDataFromPasteboard(NSPasteboard* aPasteboard, NSString* aType)
84   NSData *data = nil;
85   @try {
86     data = [aPasteboard dataForType:aType];
87   } @catch (NSException* e) {
88     NS_WARNING(nsPrintfCString(256, "Exception raised while getting data from the pasteboard: \"%s - %s\"", 
89                                [[e name] UTF8String], [[e reason] UTF8String]).get());
90   }
91   return data;
95 NS_IMETHODIMP
96 nsClipboard::SetNativeClipboardData(PRInt32 aWhichClipboard)
98   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
100   if ((aWhichClipboard != kGlobalClipboard) || !mTransferable)
101     return NS_ERROR_FAILURE;
103   mIgnoreEmptyNotification = PR_TRUE;
105   NSDictionary* pasteboardOutputDict = PasteboardDictFromTransferable(mTransferable);
106   if (!pasteboardOutputDict)
107     return NS_ERROR_FAILURE;
109   // write everything out to the general pasteboard
110   unsigned int outputCount = [pasteboardOutputDict count];
111   NSArray* outputKeys = [pasteboardOutputDict allKeys];
112   NSPasteboard* generalPBoard = [NSPasteboard generalPasteboard];
113   [generalPBoard declareTypes:outputKeys owner:nil];
114   for (unsigned int i = 0; i < outputCount; i++) {
115     NSString* currentKey = [outputKeys objectAtIndex:i];
116     id currentValue = [pasteboardOutputDict valueForKey:currentKey];
117     if (currentKey == NSStringPboardType ||
118         currentKey == NSHTMLPboardType ||
119         currentKey == kCorePboardType_url ||
120         currentKey == kCorePboardType_urld ||
121         currentKey == kCorePboardType_urln)
122       [generalPBoard setString:currentValue forType:currentKey];
123     else
124       [generalPBoard setData:currentValue forType:currentKey];
125   }
127   mChangeCount = [generalPBoard changeCount];
129   mIgnoreEmptyNotification = PR_FALSE;
131   return NS_OK;
133   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
137 NS_IMETHODIMP
138 nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, PRInt32 aWhichClipboard)
140   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
142   if ((aWhichClipboard != kGlobalClipboard) || !aTransferable)
143     return NS_ERROR_FAILURE;
145   NSPasteboard* cocoaPasteboard = [NSPasteboard generalPasteboard];
146   if (!cocoaPasteboard)
147     return NS_ERROR_FAILURE;
149   // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
150   nsCOMPtr<nsISupportsArray> flavorList;
151   nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
152   if (NS_FAILED(rv))
153     return NS_ERROR_FAILURE;
155   PRUint32 flavorCount;
156   flavorList->Count(&flavorCount);
158   // If we were the last ones to put something on the pasteboard, then just use the cached
159   // transferable. Otherwise clear it because it isn't relevant any more.
160   if (mChangeCount == [cocoaPasteboard changeCount]) {
161     if (mTransferable) {
162       for (PRUint32 i = 0; i < flavorCount; i++) {
163         nsCOMPtr<nsISupports> genericFlavor;
164         flavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
165         nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
166         if (!currentFlavor)
167           continue;
169         nsXPIDLCString flavorStr;
170         currentFlavor->ToString(getter_Copies(flavorStr));
172         nsCOMPtr<nsISupports> dataSupports;
173         PRUint32 dataSize = 0;
174         rv = mTransferable->GetTransferData(flavorStr, getter_AddRefs(dataSupports), &dataSize);
175         if (NS_SUCCEEDED(rv)) {
176           aTransferable->SetTransferData(flavorStr, dataSupports, dataSize);
177           return NS_OK; // maybe try to fill in more types? Is there a point?
178         }
179       }
180     }
181   }
182   else {
183     nsBaseClipboard::EmptyClipboard(kGlobalClipboard);
184   }
186   // at this point we can't satisfy the request from cache data so let's look
187   // for things other people put on the system clipboard
189   for (PRUint32 i = 0; i < flavorCount; i++) {
190     nsCOMPtr<nsISupports> genericFlavor;
191     flavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
192     nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
193     if (!currentFlavor)
194       continue;
196     nsXPIDLCString flavorStr;
197     currentFlavor->ToString(getter_Copies(flavorStr)); // i has a flavr
199     // printf("looking for clipboard data of type %s\n", flavorStr.get());
201     const NSString *pboardType;
202     if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
203       NSString* pString = [cocoaPasteboard stringForType:pboardType];
204       if (!pString)
205         continue;
207       NSData* stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
208       unsigned int dataLength = [stringData length];
209       void* clipboardDataPtr = malloc(dataLength);
210       if (!clipboardDataPtr)
211         return NS_ERROR_OUT_OF_MEMORY;
212       [stringData getBytes:clipboardDataPtr];
214       // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
215       PRInt32 signedDataLength = dataLength;
216       nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr, &signedDataLength);
217       dataLength = signedDataLength;
219       // skip BOM (Byte Order Mark to distinguish little or big endian)      
220       PRUnichar* clipboardDataPtrNoBOM = (PRUnichar*)clipboardDataPtr;
221       if ((dataLength > 2) &&
222           ((clipboardDataPtrNoBOM[0] == 0xFEFF) ||
223            (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
224         dataLength -= sizeof(PRUnichar);
225         clipboardDataPtrNoBOM += 1;
226       }
228       nsCOMPtr<nsISupports> genericDataWrapper;
229       nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
230                                                  getter_AddRefs(genericDataWrapper));
231       aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
232       free(clipboardDataPtr);
233       break;
234     }
235     else if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
236              flavorStr.EqualsLiteral(kPNGImageMime) ||
237              flavorStr.EqualsLiteral(kGIFImageMime)) {
238       // Figure out if there's data on the pasteboard we can grab (sanity check)
239       NSString *type = [cocoaPasteboard availableTypeFromArray:[NSArray arrayWithObjects:IMAGE_PASTEBOARD_TYPES]];
240       if (!type)
241         continue;
243       // Read data off the clipboard
244       NSData *pasteboardData = GetDataFromPasteboard(cocoaPasteboard, type);
245       if (!pasteboardData)
246         continue;
248       // Figure out what type we're converting to
249       CFStringRef outputType = NULL; 
250       if (flavorStr.EqualsLiteral(kJPEGImageMime))
251         outputType = CFSTR("public.jpeg");
252       else if (flavorStr.EqualsLiteral(kPNGImageMime))
253         outputType = CFSTR("public.png");
254       else if (flavorStr.EqualsLiteral(kGIFImageMime))
255         outputType = CFSTR("com.compuserve.gif");
256       else
257         continue;
259       // Use ImageIO to interpret the data on the clipboard and transcode.
260       // Note that ImageIO, like all CF APIs, allows NULLs to propagate freely
261       // and safely in most cases (like ObjC). A notable exception is CFRelease.
262       NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
263                                 (NSNumber*)kCFBooleanTrue, kCGImageSourceShouldAllowFloat,
264                                 (type == NSTIFFPboardType ? @"public.tiff" : @"public.png"),
265                                 kCGImageSourceTypeIdentifierHint, nil];
267       CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)pasteboardData, 
268                                                             (CFDictionaryRef)options);
269       NSMutableData *encodedData = [NSMutableData data];
270       CGImageDestinationRef dest = CGImageDestinationCreateWithData((CFMutableDataRef)encodedData,
271                                                                     outputType,
272                                                                     1, NULL);
273       CGImageDestinationAddImageFromSource(dest, source, 0, NULL);
274       PRBool successfullyConverted = CGImageDestinationFinalize(dest);
276       if (successfullyConverted) {
277         // Put the converted data in a form Gecko can understand
278         nsCOMPtr<nsIInputStream> byteStream;
279         NS_NewByteInputStream(getter_AddRefs(byteStream), (const char*)[encodedData bytes],
280                                    [encodedData length], NS_ASSIGNMENT_COPY);
281   
282         aTransferable->SetTransferData(flavorStr, byteStream, sizeof(nsIInputStream*));
283       }
285       if (dest)
286         CFRelease(dest);
287       if (source)
288         CFRelease(source);
289       
290       if (successfullyConverted)
291         break;
292       else
293         continue;
294     }
295   }
297   return NS_OK;
299   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
303 // returns true if we have *any* of the passed in flavors available for pasting
304 NS_IMETHODIMP
305 nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, PRUint32 aLength,
306                                     PRInt32 aWhichClipboard, PRBool* outResult)
308   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
310   *outResult = PR_FALSE;
312   if ((aWhichClipboard != kGlobalClipboard) || !aFlavorList)
313     return NS_OK;
315   // first see if we have data for this in our cached transferable
316   if (mTransferable) {    
317     nsCOMPtr<nsISupportsArray> transferableFlavorList;
318     nsresult rv = mTransferable->FlavorsTransferableCanImport(getter_AddRefs(transferableFlavorList));
319     if (NS_SUCCEEDED(rv)) {
320       PRUint32 transferableFlavorCount;
321       transferableFlavorList->Count(&transferableFlavorCount);
322       for (PRUint32 j = 0; j < transferableFlavorCount; j++) {
323         nsCOMPtr<nsISupports> transferableFlavorSupports;
324         transferableFlavorList->GetElementAt(j, getter_AddRefs(transferableFlavorSupports));
325         nsCOMPtr<nsISupportsCString> currentTransferableFlavor(do_QueryInterface(transferableFlavorSupports));
326         if (!currentTransferableFlavor)
327           continue;
328         nsXPIDLCString transferableFlavorStr;
329         currentTransferableFlavor->ToString(getter_Copies(transferableFlavorStr));
331         for (PRUint32 k = 0; k < aLength; k++) {
332           if (transferableFlavorStr.Equals(aFlavorList[k])) {
333             *outResult = PR_TRUE;
334             return NS_OK;
335           }
336         }
337       }      
338     }    
339   }
341   NSPasteboard* generalPBoard = [NSPasteboard generalPasteboard];
343   for (PRUint32 i = 0; i < aLength; i++) {
344     nsDependentCString mimeType(aFlavorList[i]);
345     const NSString *pboardType;
347     if (nsClipboard::IsStringType(mimeType, &pboardType)) {
348       NSString* availableType = [generalPBoard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
349       if (availableType && [availableType isEqualToString:pboardType]) {
350         *outResult = PR_TRUE;
351         break;
352       }
353     } else if (!strcmp(aFlavorList[i], kJPEGImageMime) ||
354                !strcmp(aFlavorList[i], kPNGImageMime) ||
355                !strcmp(aFlavorList[i], kGIFImageMime)) {
356       NSString* availableType = [generalPBoard availableTypeFromArray:
357                                   [NSArray arrayWithObjects:IMAGE_PASTEBOARD_TYPES]];
358       if (availableType) {
359         *outResult = PR_TRUE;
360         break;
361       }
362     }
363   }
365   return NS_OK;
367   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
371 // This function converts anything that other applications might understand into the system format
372 // and puts it into a dictionary which it returns.
373 // static
374 NSDictionary* 
375 nsClipboard::PasteboardDictFromTransferable(nsITransferable* aTransferable)
377   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
379   if (!aTransferable)
380     return nil;
382   NSMutableDictionary* pasteboardOutputDict = [NSMutableDictionary dictionary];
384   nsCOMPtr<nsISupportsArray> flavorList;
385   nsresult rv = aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
386   if (NS_FAILED(rv))
387     return nil;
389   PRUint32 flavorCount;
390   flavorList->Count(&flavorCount);
391   for (PRUint32 i = 0; i < flavorCount; i++) {
392     nsCOMPtr<nsISupports> genericFlavor;
393     flavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
394     nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
395     if (!currentFlavor)
396       continue;
398     nsXPIDLCString flavorStr;
399     currentFlavor->ToString(getter_Copies(flavorStr));
401     PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("writing out clipboard data of type %s (%d)\n", flavorStr.get(), i));
403     const NSString *pboardType;
405     if (nsClipboard::IsStringType(flavorStr, &pboardType)) {
406       void* data = nsnull;
407       PRUint32 dataSize = 0;
408       nsCOMPtr<nsISupports> genericDataWrapper;
409       rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericDataWrapper), &dataSize);
410       nsPrimitiveHelpers::CreateDataFromPrimitive(flavorStr, genericDataWrapper, &data, dataSize);
411       
412       NSString* nativeString = [NSString stringWithCharacters:(const unichar*)data length:(dataSize / sizeof(PRUnichar))];
413       // be nice to Carbon apps, normalize the receiver's contents using Form C.
414       nativeString = [nativeString precomposedStringWithCanonicalMapping];
416       [pasteboardOutputDict setObject:nativeString forKey:pboardType];
417       
418       nsMemory::Free(data);
419     }
420     else if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
421              flavorStr.EqualsLiteral(kGIFImageMime) || flavorStr.EqualsLiteral(kNativeImageMime)) {
422       PRUint32 dataSize = 0;
423       nsCOMPtr<nsISupports> transferSupports;
424       aTransferable->GetTransferData(flavorStr, getter_AddRefs(transferSupports), &dataSize);
425       nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive(do_QueryInterface(transferSupports));
426       if (!ptrPrimitive)
427         continue;
429       nsCOMPtr<nsISupports> primitiveData;
430       ptrPrimitive->GetData(getter_AddRefs(primitiveData));
432       nsCOMPtr<nsIImage> image(do_QueryInterface(primitiveData));
433       if (!image) {
434         NS_WARNING("Image isn't an nsIImage in transferable");
435         continue;
436       }
438       if (NS_FAILED(image->LockImagePixels(PR_FALSE)))
439         continue;
441       PRInt32 height = image->GetHeight();
442       PRInt32 stride = image->GetLineStride();
443       PRInt32 width = image->GetWidth();
444       if ((stride % 4 != 0) || (height < 1) || (width < 1))
445         continue;
447       // Create a CGImageRef with the bits from the image, taking into account
448       // the alpha ordering and endianness of the machine so we don't have to
449       // touch the bits ourselves.
450       CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL,
451                                                                     image->GetBits(),
452                                                                     stride * height,
453                                                                     NULL);
454       CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
455       CGImageRef imageRef = CGImageCreate(width,
456                                           height,
457                                           8,
458                                           32,
459                                           stride,
460                                           colorSpace,
461                                           kCGBitmapByteOrder32Host | kCGImageAlphaFirst,
462                                           dataProvider,
463                                           NULL,
464                                           0,
465                                           kCGRenderingIntentDefault);
466       CGColorSpaceRelease(colorSpace);
467       CGDataProviderRelease(dataProvider);
469       // Convert the CGImageRef to TIFF data.
470       CFMutableDataRef tiffData = CFDataCreateMutable(kCFAllocatorDefault, 0);
471       CGImageDestinationRef destRef = CGImageDestinationCreateWithData(tiffData,
472                                                                        CFSTR("public.tiff"),
473                                                                        1,
474                                                                        NULL);
475       CGImageDestinationAddImage(destRef, imageRef, NULL);
476       PRBool successfullyConverted = CGImageDestinationFinalize(destRef);
478       CGImageRelease(imageRef);
479       if (destRef)
480         CFRelease(destRef);
482       if (NS_FAILED(image->UnlockImagePixels(PR_FALSE)) || !successfullyConverted) {
483         if (tiffData)
484           CFRelease(tiffData);
485         continue;
486       }
488       [pasteboardOutputDict setObject:(NSMutableData*)tiffData forKey:NSTIFFPboardType];
489       if (tiffData)
490         CFRelease(tiffData);
491     }
492     else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
493       [pasteboardOutputDict setObject:[NSArray arrayWithObject:@""] forKey:NSFilesPromisePboardType];      
494     }
495     else if (flavorStr.EqualsLiteral(kURLMime)) {
496       PRUint32 len = 0;
497       nsCOMPtr<nsISupports> genericURL;
498       rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(genericURL), &len);
499       nsCOMPtr<nsISupportsString> urlObject(do_QueryInterface(genericURL));
501       nsAutoString url;
502       urlObject->GetData(url);
504       // A newline embedded in the URL means that the form is actually URL + title.
505       PRInt32 newlinePos = url.FindChar(PRUnichar('\n'));
506       if (newlinePos >= 0) {
507         url.Truncate(newlinePos);
509         nsAutoString urlTitle;
510         urlObject->GetData(urlTitle);
511         urlTitle.Mid(urlTitle, newlinePos + 1, len - (newlinePos + 1));
513         NSString *nativeTitle = [[NSString alloc] initWithCharacters:urlTitle.get() length:urlTitle.Length()];
514         // be nice to Carbon apps, normalize the receiver's contents using Form C.
515         [pasteboardOutputDict setObject:[nativeTitle precomposedStringWithCanonicalMapping] forKey:kCorePboardType_urln];
516         // Also put the title out as 'urld', since some recipients will look for that.
517         [pasteboardOutputDict setObject:[nativeTitle precomposedStringWithCanonicalMapping] forKey:kCorePboardType_urld];
518         [nativeTitle release];
519       }
521       // The Finder doesn't like getting random binary data aka
522       // Unicode, so change it into an escaped URL containing only
523       // ASCII.
524       nsCAutoString utf8Data = NS_ConvertUTF16toUTF8(url.get(), url.Length());
525       nsCAutoString escData;
526       NS_EscapeURL(utf8Data.get(), utf8Data.Length(), esc_OnlyNonASCII|esc_AlwaysCopy, escData);
528       // printf("Escaped url is %s, length %d\n", escData.get(), escData.Length());
530       NSString *nativeURL = [NSString stringWithUTF8String:escData.get()];
531       [pasteboardOutputDict setObject:nativeURL forKey:kCorePboardType_url];
532     }
533     // If it wasn't a type that we recognize as exportable we don't put it on the system
534     // clipboard. We'll just access it from our cached transferable when we need it.
535   }
537   return pasteboardOutputDict;
539   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
542 PRBool nsClipboard::IsStringType(const nsCString& aMIMEType, const NSString** aPasteboardType)
544   if (aMIMEType.EqualsLiteral(kUnicodeMime) ||
545       aMIMEType.EqualsLiteral(kHTMLMime)) {
546     if (aMIMEType.EqualsLiteral(kUnicodeMime))
547       *aPasteboardType = NSStringPboardType;
548     else
549       *aPasteboardType = NSHTMLPboardType;
550     return PR_TRUE;
551   } else {
552     return PR_FALSE;
553   }