bug 818009 - canActivate: only click-to-play-type plugins are valid r=jaws
[gecko.git] / widget / cocoa / nsDragService.mm
blobdb45dd2418768c2623ff38cfb39c0d1ee9d2a912
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 #ifdef MOZ_LOGGING
7 #define FORCE_PR_LOG
8 #endif
9 #include "prlog.h"
11 #include "nsDragService.h"
12 #include "nsObjCExceptions.h"
13 #include "nsITransferable.h"
14 #include "nsString.h"
15 #include "nsClipboard.h"
16 #include "nsXPCOM.h"
17 #include "nsISupportsPrimitives.h"
18 #include "nsCOMPtr.h"
19 #include "nsPrimitiveHelpers.h"
20 #include "nsLinebreakConverter.h"
21 #include "nsIMacUtils.h"
22 #include "nsIDOMNode.h"
23 #include "nsRect.h"
24 #include "nsPoint.h"
25 #include "nsICharsetConverterManager.h"
26 #include "nsIIOService.h"
27 #include "nsNetUtil.h"
28 #include "nsIDocument.h"
29 #include "nsIContent.h"
30 #include "nsIView.h"
31 #include "gfxASurface.h"
32 #include "gfxContext.h"
33 #include "nsCocoaUtils.h"
35 #ifdef PR_LOGGING
36 extern PRLogModuleInfo* sCocoaLog;
37 #endif
39 extern void EnsureLogInitialized();
41 extern NSPasteboard* globalDragPboard;
42 extern NSView* gLastDragView;
43 extern NSEvent* gLastDragMouseDownEvent;
44 extern bool gUserCancelledDrag;
46 // This global makes the transferable array available to Cocoa's promised
47 // file destination callback.
48 nsISupportsArray *gDraggedTransferables = nullptr;
50 NSString* const kWildcardPboardType = @"MozillaWildcard";
51 NSString* const kCorePboardType_url  = @"CorePasteboardFlavorType 0x75726C20"; // 'url '  url
52 NSString* const kCorePboardType_urld = @"CorePasteboardFlavorType 0x75726C64"; // 'urld'  desc
53 NSString* const kCorePboardType_urln = @"CorePasteboardFlavorType 0x75726C6E"; // 'urln'  title
55 nsDragService::nsDragService()
57   mNativeDragView = nil;
58   mNativeDragEvent = nil;
60   EnsureLogInitialized();
63 nsDragService::~nsDragService()
67 static nsresult SetUpDragClipboard(nsISupportsArray* aTransferableArray)
69   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
71   if (!aTransferableArray)
72     return NS_ERROR_FAILURE;
74   uint32_t count = 0;
75   aTransferableArray->Count(&count);
77   NSPasteboard* dragPBoard = [NSPasteboard pasteboardWithName:NSDragPboard];
79   for (uint32_t i = 0; i < count; i++) {
80     nsCOMPtr<nsISupports> currentTransferableSupports;
81     aTransferableArray->GetElementAt(i, getter_AddRefs(currentTransferableSupports));
82     if (!currentTransferableSupports)
83       return NS_ERROR_FAILURE;
85     nsCOMPtr<nsITransferable> currentTransferable(do_QueryInterface(currentTransferableSupports));
86     if (!currentTransferable)
87       return NS_ERROR_FAILURE;
89     // Transform the transferable to an NSDictionary
90     NSDictionary* pasteboardOutputDict = nsClipboard::PasteboardDictFromTransferable(currentTransferable);
91     if (!pasteboardOutputDict)
92       return NS_ERROR_FAILURE;
94     // write everything out to the general pasteboard
95     unsigned int typeCount = [pasteboardOutputDict count];
96     NSMutableArray* types = [NSMutableArray arrayWithCapacity:typeCount + 1];
97     [types addObjectsFromArray:[pasteboardOutputDict allKeys]];
98     // Gecko is initiating this drag so we always want its own views to consider
99     // it. Add our wildcard type to the pasteboard to accomplish this.
100     [types addObject:kWildcardPboardType]; // we don't increase the count for the loop below on purpose
101     [dragPBoard declareTypes:types owner:nil];
102     for (unsigned int i = 0; i < typeCount; i++) {
103       NSString* currentKey = [types objectAtIndex:i];
104       id currentValue = [pasteboardOutputDict valueForKey:currentKey];
105       if (currentKey == NSStringPboardType ||
106           currentKey == kCorePboardType_url ||
107           currentKey == kCorePboardType_urld ||
108           currentKey == kCorePboardType_urln) {
109         [dragPBoard setString:currentValue forType:currentKey];
110       }
111       else if (currentKey == NSHTMLPboardType) {
112         [dragPBoard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
113                       forType:currentKey];
114       }
115       else if (currentKey == NSTIFFPboardType) {
116         [dragPBoard setData:currentValue forType:currentKey];
117       }
118       else if (currentKey == NSFilesPromisePboardType) {
119         [dragPBoard setPropertyList:currentValue forType:currentKey];        
120       }
121     }
122   }
124   return NS_OK;
126   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
129 NSImage*
130 nsDragService::ConstructDragImage(nsIDOMNode* aDOMNode,
131                                   nsIntRect* aDragRect,
132                                   nsIScriptableRegion* aRegion)
134   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
136   NSPoint screenPoint =
137     [[gLastDragView window] convertBaseToScreen:
138       [gLastDragMouseDownEvent locationInWindow]];
139   // Y coordinates are bottom to top, so reverse this
140   screenPoint.y = nsCocoaUtils::FlippedScreenY(screenPoint.y);
142   CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(gLastDragView);
143   nsIntPoint pt =
144     nsCocoaUtils::CocoaPointsToDevPixels(NSMakePoint(screenPoint.x,
145                                                      screenPoint.y),
146                                          scaleFactor);
148   nsRefPtr<gfxASurface> surface;
149   nsPresContext* pc;
150   nsresult rv = DrawDrag(aDOMNode, aRegion, pt.x, pt.y,
151                          aDragRect, getter_AddRefs(surface), &pc);
152   if (!aDragRect->width || !aDragRect->height) {
153     // just use some suitable defaults
154     int32_t size = nsCocoaUtils::CocoaPointsToDevPixels(20, scaleFactor);
155     aDragRect->SetRect(pt.x, pt.y, size, size);
156   }
158   if (NS_FAILED(rv) || !surface)
159     return nil;
161   uint32_t width = aDragRect->width;
162   uint32_t height = aDragRect->height;
164   nsRefPtr<gfxImageSurface> imgSurface = new gfxImageSurface(
165     gfxIntSize(width, height), gfxImageSurface::ImageFormatARGB32);
166   if (!imgSurface)
167     return nil;
169   nsRefPtr<gfxContext> context = new gfxContext(imgSurface);
170   if (!context)
171     return nil;
173   context->SetOperator(gfxContext::OPERATOR_SOURCE);
174   context->SetSource(surface);
175   context->Paint();
177   uint32_t* imageData = (uint32_t*)imgSurface->Data();
178   int32_t stride = imgSurface->Stride();
180   NSBitmapImageRep* imageRep =
181     [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
182                                             pixelsWide:width
183                                             pixelsHigh:height
184                                          bitsPerSample:8
185                                        samplesPerPixel:4
186                                               hasAlpha:YES
187                                               isPlanar:NO
188                                         colorSpaceName:NSDeviceRGBColorSpace
189                                            bytesPerRow:width * 4
190                                           bitsPerPixel:32];
192   uint8_t* dest = [imageRep bitmapData];
193   for (uint32_t i = 0; i < height; ++i) {
194     uint8_t* src = (uint8_t *)imageData + i * stride;
195     for (uint32_t j = 0; j < width; ++j) {
196       // Reduce transparency overall by multipying by a factor. Remember, Alpha
197       // is premultipled here. Also, Quartz likes RGBA, so do that translation as well.
198 #ifdef IS_BIG_ENDIAN
199       dest[0] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
200       dest[1] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
201       dest[2] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
202       dest[3] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
203 #else
204       dest[0] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
205       dest[1] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
206       dest[2] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
207       dest[3] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
208 #endif
209       src += 4;
210       dest += 4;
211     }
212   }
214   NSImage* image =
215     [[NSImage alloc] initWithSize:NSMakeSize(width / scaleFactor,
216                                              height / scaleFactor)];
217   [image addRepresentation:imageRep];
218   [imageRep release];
220   return [image autorelease];
222   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
225 // We can only invoke NSView's 'dragImage:at:offset:event:pasteboard:source:slideBack:' from
226 // within NSView's 'mouseDown:' or 'mouseDragged:'. Luckily 'mouseDragged' is always on the
227 // stack when InvokeDragSession gets called.
228 NS_IMETHODIMP
229 nsDragService::InvokeDragSession(nsIDOMNode* aDOMNode, nsISupportsArray* aTransferableArray,
230                                  nsIScriptableRegion* aDragRgn, uint32_t aActionType)
232   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
234   nsresult rv = nsBaseDragService::InvokeDragSession(aDOMNode,
235                                                      aTransferableArray,
236                                                      aDragRgn, aActionType);
237   NS_ENSURE_SUCCESS(rv, rv);
239   mDataItems = aTransferableArray;
241   // put data on the clipboard
242   if (NS_FAILED(SetUpDragClipboard(aTransferableArray)))
243     return NS_ERROR_FAILURE;
245   nsIntRect dragRect(0, 0, 20, 20);
246   NSImage* image = ConstructDragImage(aDOMNode, &dragRect, aDragRgn);
247   if (!image) {
248     // if no image was returned, just draw a rectangle
249     NSSize size;
250     size.width = dragRect.width;
251     size.height = dragRect.height;
252     image = [[NSImage alloc] initWithSize:size];
253     [image lockFocus];
254     [[NSColor grayColor] set];
255     NSBezierPath* path = [NSBezierPath bezierPath];
256     [path setLineWidth:2.0];
257     [path moveToPoint:NSMakePoint(0, 0)];
258     [path lineToPoint:NSMakePoint(0, size.height)];
259     [path lineToPoint:NSMakePoint(size.width, size.height)];
260     [path lineToPoint:NSMakePoint(size.width, 0)];
261     [path lineToPoint:NSMakePoint(0, 0)];
262     [path stroke];
263     [image unlockFocus];
264   }
266   nsIntPoint pt(dragRect.x, dragRect.YMost());
267   CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(gLastDragView);
268   NSPoint point = nsCocoaUtils::DevPixelsToCocoaPoints(pt, scaleFactor);
269   point.y = nsCocoaUtils::FlippedScreenY(point.y);
271   point = [[gLastDragView window] convertScreenToBase: point];
272   NSPoint localPoint = [gLastDragView convertPoint:point fromView:nil];
274   // Save the transferables away in case a promised file callback is invoked.
275   gDraggedTransferables = aTransferableArray;
277   nsBaseDragService::StartDragSession();
278   nsBaseDragService::OpenDragPopup();
280   // We need to retain the view and the event during the drag in case either gets destroyed.
281   mNativeDragView = [gLastDragView retain];
282   mNativeDragEvent = [gLastDragMouseDownEvent retain];
284   gUserCancelledDrag = false;
285   [mNativeDragView dragImage:image
286                           at:localPoint
287                       offset:NSZeroSize
288                        event:mNativeDragEvent
289                   pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
290                       source:mNativeDragView
291                    slideBack:YES];
292   gUserCancelledDrag = false;
294   if (mDoingDrag)
295     nsBaseDragService::EndDragSession(false);
296   
297   return NS_OK;
299   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
302 NS_IMETHODIMP
303 nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex)
305   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
307   if (!aTransferable)
308     return NS_ERROR_FAILURE;
310   // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
311   nsCOMPtr<nsISupportsArray> flavorList;
312   nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
313   if (NS_FAILED(rv))
314     return NS_ERROR_FAILURE;
316   uint32_t acceptableFlavorCount;
317   flavorList->Count(&acceptableFlavorCount);
319   // if this drag originated within Mozilla we should just use the cached data from
320   // when the drag started if possible
321   if (mDataItems) {
322     nsCOMPtr<nsISupports> currentTransferableSupports;
323     mDataItems->GetElementAt(aItemIndex, getter_AddRefs(currentTransferableSupports));
324     if (currentTransferableSupports) {
325       nsCOMPtr<nsITransferable> currentTransferable(do_QueryInterface(currentTransferableSupports));
326       if (currentTransferable) {
327         for (uint32_t i = 0; i < acceptableFlavorCount; i++) {
328           nsCOMPtr<nsISupports> genericFlavor;
329           flavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
330           nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
331           if (!currentFlavor)
332             continue;
333           nsXPIDLCString flavorStr;
334           currentFlavor->ToString(getter_Copies(flavorStr));
336           nsCOMPtr<nsISupports> dataSupports;
337           uint32_t dataSize = 0;
338           rv = currentTransferable->GetTransferData(flavorStr, getter_AddRefs(dataSupports), &dataSize);
339           if (NS_SUCCEEDED(rv)) {
340             aTransferable->SetTransferData(flavorStr, dataSupports, dataSize);
341             return NS_OK; // maybe try to fill in more types? Is there a point?
342           }
343         }
344       }
345     }
346   }
348   // now check the actual clipboard for data
349   for (uint32_t i = 0; i < acceptableFlavorCount; i++) {
350     nsCOMPtr<nsISupports> genericFlavor;
351     flavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
352     nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
354     if (!currentFlavor)
355       continue;
357     nsXPIDLCString flavorStr;
358     currentFlavor->ToString(getter_Copies(flavorStr));
360     PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("nsDragService::GetData: looking for clipboard data of type %s\n", flavorStr.get()));
362     if (flavorStr.EqualsLiteral(kFileMime)) {
363       NSArray* pFiles = [globalDragPboard propertyListForType:NSFilenamesPboardType];
364       if (!pFiles || [pFiles count] < (aItemIndex + 1))
365         continue;
367       NSString* filePath = [pFiles objectAtIndex:aItemIndex];
368       if (!filePath)
369         continue;
371       unsigned int stringLength = [filePath length];
372       unsigned int dataLength = (stringLength + 1) * sizeof(PRUnichar); // in bytes
373       PRUnichar* clipboardDataPtr = (PRUnichar*)malloc(dataLength);
374       if (!clipboardDataPtr)
375         return NS_ERROR_OUT_OF_MEMORY;
376       [filePath getCharacters:clipboardDataPtr];
377       clipboardDataPtr[stringLength] = 0; // null terminate
379       nsCOMPtr<nsIFile> file;
380       nsresult rv = NS_NewLocalFile(nsDependentString(clipboardDataPtr), true, getter_AddRefs(file));
381       free(clipboardDataPtr);
382       if (NS_FAILED(rv))
383         continue;
385       nsCOMPtr<nsISupports> genericDataWrapper;
386       genericDataWrapper = do_QueryInterface(file);
387       aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
388       
389       break;
390     }
392     NSString *pboardType = NSStringPboardType;
394     if (nsClipboard::IsStringType(flavorStr, &pboardType) ||
395         flavorStr.EqualsLiteral(kURLMime) ||
396         flavorStr.EqualsLiteral(kURLDataMime) ||
397         flavorStr.EqualsLiteral(kURLDescriptionMime)) {
398       NSString* pString = [globalDragPboard stringForType:pboardType];
399       if (!pString)
400         continue;
402       NSData* stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
403       unsigned int dataLength = [stringData length];
404       void* clipboardDataPtr = malloc(dataLength);
405       if (!clipboardDataPtr)
406         return NS_ERROR_OUT_OF_MEMORY;
407       [stringData getBytes:clipboardDataPtr];
409       // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
410       int32_t signedDataLength = dataLength;
411       nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr, &signedDataLength);
412       dataLength = signedDataLength;
414       // skip BOM (Byte Order Mark to distinguish little or big endian)      
415       PRUnichar* clipboardDataPtrNoBOM = (PRUnichar*)clipboardDataPtr;
416       if ((dataLength > 2) &&
417           ((clipboardDataPtrNoBOM[0] == 0xFEFF) ||
418            (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
419         dataLength -= sizeof(PRUnichar);
420         clipboardDataPtrNoBOM += 1;
421       }
423       nsCOMPtr<nsISupports> genericDataWrapper;
424       nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
425                                                  getter_AddRefs(genericDataWrapper));
426       aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
427       free(clipboardDataPtr);
428       break;
429     }
431     // We have never supported this on Mac OS X, we should someday. Normally dragging images
432     // in is accomplished with a file path drag instead of the image data itself.
433     /*
434     if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
435         flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime)) {
437     }
438     */
439   }
440   return NS_OK;
442   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
445 NS_IMETHODIMP
446 nsDragService::IsDataFlavorSupported(const char *aDataFlavor, bool *_retval)
448   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
450   *_retval = false;
452   if (!globalDragPboard)
453     return NS_ERROR_FAILURE;
455   nsDependentCString dataFlavor(aDataFlavor);
457   // first see if we have data for this in our cached transferable
458   if (mDataItems) {
459     uint32_t dataItemsCount;
460     mDataItems->Count(&dataItemsCount);
461     for (unsigned int i = 0; i < dataItemsCount; i++) {
462       nsCOMPtr<nsISupports> currentTransferableSupports;
463       mDataItems->GetElementAt(i, getter_AddRefs(currentTransferableSupports));
464       if (!currentTransferableSupports)
465         continue;
467       nsCOMPtr<nsITransferable> currentTransferable(do_QueryInterface(currentTransferableSupports));
468       if (!currentTransferable)
469         continue;
471       nsCOMPtr<nsISupportsArray> flavorList;
472       nsresult rv = currentTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
473       if (NS_FAILED(rv))
474         continue;
476       uint32_t flavorCount;
477       flavorList->Count(&flavorCount);
478       for (uint32_t j = 0; j < flavorCount; j++) {
479         nsCOMPtr<nsISupports> genericFlavor;
480         flavorList->GetElementAt(j, getter_AddRefs(genericFlavor));
481         nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
482         if (!currentFlavor)
483           continue;
484         nsXPIDLCString flavorStr;
485         currentFlavor->ToString(getter_Copies(flavorStr));
486         if (dataFlavor.Equals(flavorStr)) {
487           *_retval = true;
488           return NS_OK;
489         }
490       }
491     }
492   }
494   NSString *pboardType = nil;
496   if (dataFlavor.EqualsLiteral(kFileMime)) {
497     NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:NSFilenamesPboardType]];
498     if (availableType && [availableType isEqualToString:NSFilenamesPboardType])
499       *_retval = true;
500   }
501   else if (dataFlavor.EqualsLiteral(kURLMime)) {
502     NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:kCorePboardType_url]];
503     if (availableType && [availableType isEqualToString:kCorePboardType_url])
504       *_retval = true;
505   }
506   else if (nsClipboard::IsStringType(dataFlavor, &pboardType)) {
507     NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
508     if (availableType && [availableType isEqualToString:pboardType])
509       *_retval = true;
510   }
512   return NS_OK;
514   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
517 NS_IMETHODIMP
518 nsDragService::GetNumDropItems(uint32_t* aNumItems)
520   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
522   *aNumItems = 0;
524   // first check to see if we have a number of items cached
525   if (mDataItems) {
526     mDataItems->Count(aNumItems);
527     return NS_OK;
528   }
530   // if there is a clipboard and there is something on it, then there is at least 1 item
531   NSArray* clipboardTypes = [globalDragPboard types];
532   if (globalDragPboard && [clipboardTypes count] > 0)
533     *aNumItems = 1;
534   else 
535     return NS_OK;
536   
537   // if there is a list of files, send the number of files in that list
538   NSArray* fileNames = [globalDragPboard propertyListForType:NSFilenamesPboardType];
539   if (fileNames)
540     *aNumItems = [fileNames count];
541   
542   return NS_OK;
544   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
547 NS_IMETHODIMP
548 nsDragService::EndDragSession(bool aDoneDrag)
550   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
552   if (mNativeDragView) {
553     [mNativeDragView release];
554     mNativeDragView = nil;
555   }
556   if (mNativeDragEvent) {
557     [mNativeDragEvent release];
558     mNativeDragEvent = nil;
559   }
561   mUserCancelled = gUserCancelledDrag;
563   nsresult rv = nsBaseDragService::EndDragSession(aDoneDrag);
564   mDataItems = nullptr;
565   return rv;
567   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;