Bumping gaia.json for 2 gaia revision(s) a=gaia-bump
[gecko.git] / widget / cocoa / nsDragService.mm
blob043a5cf95e801c71dbf0351582732c1a5a5cc5f5
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 "nsIIOService.h"
26 #include "nsNetUtil.h"
27 #include "nsIDocument.h"
28 #include "nsIContent.h"
29 #include "nsView.h"
30 #include "gfxContext.h"
31 #include "nsCocoaUtils.h"
32 #include "mozilla/gfx/2D.h"
33 #include "gfxPlatform.h"
35 using namespace mozilla;
36 using namespace mozilla::gfx;
38 #ifdef PR_LOGGING
39 extern PRLogModuleInfo* sCocoaLog;
40 #endif
42 extern void EnsureLogInitialized();
44 extern NSPasteboard* globalDragPboard;
45 extern NSView* gLastDragView;
46 extern NSEvent* gLastDragMouseDownEvent;
47 extern bool gUserCancelledDrag;
49 // This global makes the transferable array available to Cocoa's promised
50 // file destination callback.
51 nsISupportsArray *gDraggedTransferables = nullptr;
53 NSString* const kWildcardPboardType = @"MozillaWildcard";
54 NSString* const kCorePboardType_url  = @"CorePasteboardFlavorType 0x75726C20"; // 'url '  url
55 NSString* const kCorePboardType_urld = @"CorePasteboardFlavorType 0x75726C64"; // 'urld'  desc
56 NSString* const kCorePboardType_urln = @"CorePasteboardFlavorType 0x75726C6E"; // 'urln'  title
58 nsDragService::nsDragService()
60   mNativeDragView = nil;
61   mNativeDragEvent = nil;
63   EnsureLogInitialized();
66 nsDragService::~nsDragService()
70 static nsresult SetUpDragClipboard(nsISupportsArray* aTransferableArray)
72   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
74   if (!aTransferableArray)
75     return NS_ERROR_FAILURE;
77   uint32_t count = 0;
78   aTransferableArray->Count(&count);
80   NSPasteboard* dragPBoard = [NSPasteboard pasteboardWithName:NSDragPboard];
82   for (uint32_t i = 0; i < count; i++) {
83     nsCOMPtr<nsISupports> currentTransferableSupports;
84     aTransferableArray->GetElementAt(i, getter_AddRefs(currentTransferableSupports));
85     if (!currentTransferableSupports)
86       return NS_ERROR_FAILURE;
88     nsCOMPtr<nsITransferable> currentTransferable(do_QueryInterface(currentTransferableSupports));
89     if (!currentTransferable)
90       return NS_ERROR_FAILURE;
92     // Transform the transferable to an NSDictionary
93     NSDictionary* pasteboardOutputDict = nsClipboard::PasteboardDictFromTransferable(currentTransferable);
94     if (!pasteboardOutputDict)
95       return NS_ERROR_FAILURE;
97     // write everything out to the general pasteboard
98     unsigned int typeCount = [pasteboardOutputDict count];
99     NSMutableArray* types = [NSMutableArray arrayWithCapacity:typeCount + 1];
100     [types addObjectsFromArray:[pasteboardOutputDict allKeys]];
101     // Gecko is initiating this drag so we always want its own views to consider
102     // it. Add our wildcard type to the pasteboard to accomplish this.
103     [types addObject:kWildcardPboardType]; // we don't increase the count for the loop below on purpose
104     [dragPBoard declareTypes:types owner:nil];
105     for (unsigned int i = 0; i < typeCount; i++) {
106       NSString* currentKey = [types objectAtIndex:i];
107       id currentValue = [pasteboardOutputDict valueForKey:currentKey];
108       if (currentKey == NSStringPboardType ||
109           currentKey == kCorePboardType_url ||
110           currentKey == kCorePboardType_urld ||
111           currentKey == kCorePboardType_urln) {
112         [dragPBoard setString:currentValue forType:currentKey];
113       }
114       else if (currentKey == NSHTMLPboardType) {
115         [dragPBoard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
116                       forType:currentKey];
117       }
118       else if (currentKey == NSTIFFPboardType) {
119         [dragPBoard setData:currentValue forType:currentKey];
120       }
121       else if (currentKey == NSFilesPromisePboardType ||
122                currentKey == NSFilenamesPboardType) {
123         [dragPBoard setPropertyList:currentValue forType:currentKey];        
124       }
125     }
126   }
128   return NS_OK;
130   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
133 NSImage*
134 nsDragService::ConstructDragImage(nsIDOMNode* aDOMNode,
135                                   nsIntRect* aDragRect,
136                                   nsIScriptableRegion* aRegion)
138   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
140   NSPoint screenPoint =
141     [[gLastDragView window] convertBaseToScreen:
142       [gLastDragMouseDownEvent locationInWindow]];
143   // Y coordinates are bottom to top, so reverse this
144   screenPoint.y = nsCocoaUtils::FlippedScreenY(screenPoint.y);
146   CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(gLastDragView);
148   RefPtr<SourceSurface> surface;
149   nsPresContext* pc;
150   nsresult rv = DrawDrag(aDOMNode, aRegion,
151                          NSToIntRound(screenPoint.x),
152                          NSToIntRound(screenPoint.y),
153                          aDragRect, &surface, &pc);
154   if (!aDragRect->width || !aDragRect->height) {
155     // just use some suitable defaults
156     int32_t size = nsCocoaUtils::CocoaPointsToDevPixels(20, scaleFactor);
157     aDragRect->SetRect(nsCocoaUtils::CocoaPointsToDevPixels(screenPoint.x, scaleFactor),
158                        nsCocoaUtils::CocoaPointsToDevPixels(screenPoint.y, scaleFactor),
159                        size, size);
160   }
162   if (NS_FAILED(rv) || !surface)
163     return nil;
165   uint32_t width = aDragRect->width;
166   uint32_t height = aDragRect->height;
170   RefPtr<DataSourceSurface> dataSurface =
171     Factory::CreateDataSourceSurface(IntSize(width, height),
172                                      SurfaceFormat::B8G8R8A8);
173   DataSourceSurface::MappedSurface map;
174   if (!dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
175     return nil;
176   }
178   RefPtr<DrawTarget> dt =
179     Factory::CreateDrawTargetForData(BackendType::CAIRO,
180                                      map.mData,
181                                      dataSurface->GetSize(),
182                                      map.mStride,
183                                      dataSurface->GetFormat());
184   if (!dt) {
185     dataSurface->Unmap();
186     return nil;
187   }
189   dt->FillRect(gfx::Rect(0, 0, width, height),
190                SurfacePattern(surface, ExtendMode::CLAMP),
191                DrawOptions(1.0f, CompositionOp::OP_SOURCE));
193   NSBitmapImageRep* imageRep =
194     [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
195                                             pixelsWide:width
196                                             pixelsHigh:height
197                                          bitsPerSample:8
198                                        samplesPerPixel:4
199                                               hasAlpha:YES
200                                               isPlanar:NO
201                                         colorSpaceName:NSDeviceRGBColorSpace
202                                            bytesPerRow:width * 4
203                                           bitsPerPixel:32];
205   uint8_t* dest = [imageRep bitmapData];
206   for (uint32_t i = 0; i < height; ++i) {
207     uint8_t* src = map.mData + i * map.mStride;
208     for (uint32_t j = 0; j < width; ++j) {
209       // Reduce transparency overall by multipying by a factor. Remember, Alpha
210       // is premultipled here. Also, Quartz likes RGBA, so do that translation as well.
211 #ifdef IS_BIG_ENDIAN
212       dest[0] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
213       dest[1] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
214       dest[2] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
215       dest[3] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
216 #else
217       dest[0] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
218       dest[1] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
219       dest[2] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
220       dest[3] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
221 #endif
222       src += 4;
223       dest += 4;
224     }
225   }
226   dataSurface->Unmap();
228   NSImage* image =
229     [[NSImage alloc] initWithSize:NSMakeSize(width / scaleFactor,
230                                              height / scaleFactor)];
231   [image addRepresentation:imageRep];
232   [imageRep release];
234   return [image autorelease];
236   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
239 // We can only invoke NSView's 'dragImage:at:offset:event:pasteboard:source:slideBack:' from
240 // within NSView's 'mouseDown:' or 'mouseDragged:'. Luckily 'mouseDragged' is always on the
241 // stack when InvokeDragSession gets called.
242 NS_IMETHODIMP
243 nsDragService::InvokeDragSession(nsIDOMNode* aDOMNode, nsISupportsArray* aTransferableArray,
244                                  nsIScriptableRegion* aDragRgn, uint32_t aActionType)
246   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
248   nsresult rv = nsBaseDragService::InvokeDragSession(aDOMNode,
249                                                      aTransferableArray,
250                                                      aDragRgn, aActionType);
251   NS_ENSURE_SUCCESS(rv, rv);
253   mDataItems = aTransferableArray;
255   // put data on the clipboard
256   if (NS_FAILED(SetUpDragClipboard(aTransferableArray)))
257     return NS_ERROR_FAILURE;
259   nsIntRect dragRect(0, 0, 20, 20);
260   NSImage* image = ConstructDragImage(aDOMNode, &dragRect, aDragRgn);
261   if (!image) {
262     // if no image was returned, just draw a rectangle
263     NSSize size;
264     size.width = dragRect.width;
265     size.height = dragRect.height;
266     image = [[NSImage alloc] initWithSize:size];
267     [image lockFocus];
268     [[NSColor grayColor] set];
269     NSBezierPath* path = [NSBezierPath bezierPath];
270     [path setLineWidth:2.0];
271     [path moveToPoint:NSMakePoint(0, 0)];
272     [path lineToPoint:NSMakePoint(0, size.height)];
273     [path lineToPoint:NSMakePoint(size.width, size.height)];
274     [path lineToPoint:NSMakePoint(size.width, 0)];
275     [path lineToPoint:NSMakePoint(0, 0)];
276     [path stroke];
277     [image unlockFocus];
278   }
280   nsIntPoint pt(dragRect.x, dragRect.YMost());
281   CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(gLastDragView);
282   NSPoint point = nsCocoaUtils::DevPixelsToCocoaPoints(pt, scaleFactor);
283   point.y = nsCocoaUtils::FlippedScreenY(point.y);
285   point = [[gLastDragView window] convertScreenToBase: point];
286   NSPoint localPoint = [gLastDragView convertPoint:point fromView:nil];
288   // Save the transferables away in case a promised file callback is invoked.
289   gDraggedTransferables = aTransferableArray;
291   nsBaseDragService::StartDragSession();
292   nsBaseDragService::OpenDragPopup();
294   // We need to retain the view and the event during the drag in case either gets destroyed.
295   mNativeDragView = [gLastDragView retain];
296   mNativeDragEvent = [gLastDragMouseDownEvent retain];
298   gUserCancelledDrag = false;
299   [mNativeDragView dragImage:image
300                           at:localPoint
301                       offset:NSZeroSize
302                        event:mNativeDragEvent
303                   pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
304                       source:mNativeDragView
305                    slideBack:YES];
306   gUserCancelledDrag = false;
308   if (mDoingDrag)
309     nsBaseDragService::EndDragSession(false);
310   
311   return NS_OK;
313   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
316 NS_IMETHODIMP
317 nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex)
319   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
321   if (!aTransferable)
322     return NS_ERROR_FAILURE;
324   // get flavor list that includes all acceptable flavors (including ones obtained through conversion)
325   nsCOMPtr<nsISupportsArray> flavorList;
326   nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
327   if (NS_FAILED(rv))
328     return NS_ERROR_FAILURE;
330   uint32_t acceptableFlavorCount;
331   flavorList->Count(&acceptableFlavorCount);
333   // if this drag originated within Mozilla we should just use the cached data from
334   // when the drag started if possible
335   if (mDataItems) {
336     nsCOMPtr<nsISupports> currentTransferableSupports;
337     mDataItems->GetElementAt(aItemIndex, getter_AddRefs(currentTransferableSupports));
338     if (currentTransferableSupports) {
339       nsCOMPtr<nsITransferable> currentTransferable(do_QueryInterface(currentTransferableSupports));
340       if (currentTransferable) {
341         for (uint32_t i = 0; i < acceptableFlavorCount; i++) {
342           nsCOMPtr<nsISupports> genericFlavor;
343           flavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
344           nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
345           if (!currentFlavor)
346             continue;
347           nsXPIDLCString flavorStr;
348           currentFlavor->ToString(getter_Copies(flavorStr));
350           nsCOMPtr<nsISupports> dataSupports;
351           uint32_t dataSize = 0;
352           rv = currentTransferable->GetTransferData(flavorStr, getter_AddRefs(dataSupports), &dataSize);
353           if (NS_SUCCEEDED(rv)) {
354             aTransferable->SetTransferData(flavorStr, dataSupports, dataSize);
355             return NS_OK; // maybe try to fill in more types? Is there a point?
356           }
357         }
358       }
359     }
360   }
362   // now check the actual clipboard for data
363   for (uint32_t i = 0; i < acceptableFlavorCount; i++) {
364     nsCOMPtr<nsISupports> genericFlavor;
365     flavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
366     nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
368     if (!currentFlavor)
369       continue;
371     nsXPIDLCString flavorStr;
372     currentFlavor->ToString(getter_Copies(flavorStr));
374     PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("nsDragService::GetData: looking for clipboard data of type %s\n", flavorStr.get()));
376     if (flavorStr.EqualsLiteral(kFileMime)) {
377       NSArray* pFiles = [globalDragPboard propertyListForType:NSFilenamesPboardType];
378       if (!pFiles || [pFiles count] < (aItemIndex + 1))
379         continue;
381       NSString* filePath = [pFiles objectAtIndex:aItemIndex];
382       if (!filePath)
383         continue;
385       unsigned int stringLength = [filePath length];
386       unsigned int dataLength = (stringLength + 1) * sizeof(char16_t); // in bytes
387       char16_t* clipboardDataPtr = (char16_t*)malloc(dataLength);
388       if (!clipboardDataPtr)
389         return NS_ERROR_OUT_OF_MEMORY;
390       [filePath getCharacters:reinterpret_cast<unichar*>(clipboardDataPtr)];
391       clipboardDataPtr[stringLength] = 0; // null terminate
393       nsCOMPtr<nsIFile> file;
394       nsresult rv = NS_NewLocalFile(nsDependentString(clipboardDataPtr), true, getter_AddRefs(file));
395       free(clipboardDataPtr);
396       if (NS_FAILED(rv))
397         continue;
399       aTransferable->SetTransferData(flavorStr, file, dataLength);
400       
401       break;
402     }
404     NSString *pboardType = NSStringPboardType;
406     if (nsClipboard::IsStringType(flavorStr, &pboardType) ||
407         flavorStr.EqualsLiteral(kURLMime) ||
408         flavorStr.EqualsLiteral(kURLDataMime) ||
409         flavorStr.EqualsLiteral(kURLDescriptionMime)) {
410       NSString* pString = [globalDragPboard stringForType:pboardType];
411       if (!pString)
412         continue;
414       NSData* stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
415       unsigned int dataLength = [stringData length];
416       void* clipboardDataPtr = malloc(dataLength);
417       if (!clipboardDataPtr)
418         return NS_ERROR_OUT_OF_MEMORY;
419       [stringData getBytes:clipboardDataPtr];
421       // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
422       int32_t signedDataLength = dataLength;
423       nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr, &signedDataLength);
424       dataLength = signedDataLength;
426       // skip BOM (Byte Order Mark to distinguish little or big endian)      
427       char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
428       if ((dataLength > 2) &&
429           ((clipboardDataPtrNoBOM[0] == 0xFEFF) ||
430            (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
431         dataLength -= sizeof(char16_t);
432         clipboardDataPtrNoBOM += 1;
433       }
435       nsCOMPtr<nsISupports> genericDataWrapper;
436       nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
437                                                  getter_AddRefs(genericDataWrapper));
438       aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
439       free(clipboardDataPtr);
440       break;
441     }
443     // We have never supported this on Mac OS X, we should someday. Normally dragging images
444     // in is accomplished with a file path drag instead of the image data itself.
445     /*
446     if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
447         flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime)) {
449     }
450     */
451   }
452   return NS_OK;
454   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
457 NS_IMETHODIMP
458 nsDragService::IsDataFlavorSupported(const char *aDataFlavor, bool *_retval)
460   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
462   *_retval = false;
464   if (!globalDragPboard)
465     return NS_ERROR_FAILURE;
467   nsDependentCString dataFlavor(aDataFlavor);
469   // first see if we have data for this in our cached transferable
470   if (mDataItems) {
471     uint32_t dataItemsCount;
472     mDataItems->Count(&dataItemsCount);
473     for (unsigned int i = 0; i < dataItemsCount; i++) {
474       nsCOMPtr<nsISupports> currentTransferableSupports;
475       mDataItems->GetElementAt(i, getter_AddRefs(currentTransferableSupports));
476       if (!currentTransferableSupports)
477         continue;
479       nsCOMPtr<nsITransferable> currentTransferable(do_QueryInterface(currentTransferableSupports));
480       if (!currentTransferable)
481         continue;
483       nsCOMPtr<nsISupportsArray> flavorList;
484       nsresult rv = currentTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
485       if (NS_FAILED(rv))
486         continue;
488       uint32_t flavorCount;
489       flavorList->Count(&flavorCount);
490       for (uint32_t j = 0; j < flavorCount; j++) {
491         nsCOMPtr<nsISupports> genericFlavor;
492         flavorList->GetElementAt(j, getter_AddRefs(genericFlavor));
493         nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
494         if (!currentFlavor)
495           continue;
496         nsXPIDLCString flavorStr;
497         currentFlavor->ToString(getter_Copies(flavorStr));
498         if (dataFlavor.Equals(flavorStr)) {
499           *_retval = true;
500           return NS_OK;
501         }
502       }
503     }
504   }
506   NSString *pboardType = nil;
508   if (dataFlavor.EqualsLiteral(kFileMime)) {
509     NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:NSFilenamesPboardType]];
510     if (availableType && [availableType isEqualToString:NSFilenamesPboardType])
511       *_retval = true;
512   }
513   else if (dataFlavor.EqualsLiteral(kURLMime)) {
514     NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:kCorePboardType_url]];
515     if (availableType && [availableType isEqualToString:kCorePboardType_url])
516       *_retval = true;
517   }
518   else if (nsClipboard::IsStringType(dataFlavor, &pboardType)) {
519     NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
520     if (availableType && [availableType isEqualToString:pboardType])
521       *_retval = true;
522   }
524   return NS_OK;
526   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
529 NS_IMETHODIMP
530 nsDragService::GetNumDropItems(uint32_t* aNumItems)
532   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
534   *aNumItems = 0;
536   // first check to see if we have a number of items cached
537   if (mDataItems) {
538     mDataItems->Count(aNumItems);
539     return NS_OK;
540   }
542   // if there is a clipboard and there is something on it, then there is at least 1 item
543   NSArray* clipboardTypes = [globalDragPboard types];
544   if (globalDragPboard && [clipboardTypes count] > 0)
545     *aNumItems = 1;
546   else 
547     return NS_OK;
548   
549   // if there is a list of files, send the number of files in that list
550   NSArray* fileNames = [globalDragPboard propertyListForType:NSFilenamesPboardType];
551   if (fileNames)
552     *aNumItems = [fileNames count];
553   
554   return NS_OK;
556   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
559 NS_IMETHODIMP
560 nsDragService::EndDragSession(bool aDoneDrag)
562   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
564   if (mNativeDragView) {
565     [mNativeDragView release];
566     mNativeDragView = nil;
567   }
568   if (mNativeDragEvent) {
569     [mNativeDragEvent release];
570     mNativeDragEvent = nil;
571   }
573   mUserCancelled = gUserCancelledDrag;
575   nsresult rv = nsBaseDragService::EndDragSession(aDoneDrag);
576   mDataItems = nullptr;
577   return rv;
579   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;