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/. */
11 #include "nsDragService.h"
12 #include "nsObjCExceptions.h"
13 #include "nsITransferable.h"
15 #include "nsClipboard.h"
17 #include "nsISupportsPrimitives.h"
19 #include "nsPrimitiveHelpers.h"
20 #include "nsLinebreakConverter.h"
21 #include "nsIMacUtils.h"
22 #include "nsIDOMNode.h"
25 #include "nsICharsetConverterManager.h"
26 #include "nsIIOService.h"
27 #include "nsNetUtil.h"
28 #include "nsIDocument.h"
29 #include "nsIContent.h"
31 #include "gfxASurface.h"
32 #include "gfxContext.h"
33 #include "nsCocoaUtils.h"
36 extern PRLogModuleInfo* sCocoaLog;
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;
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];
111 else if (currentKey == NSHTMLPboardType) {
112 [dragPBoard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
115 else if (currentKey == NSTIFFPboardType) {
116 [dragPBoard setData:currentValue forType:currentKey];
118 else if (currentKey == NSFilesPromisePboardType) {
119 [dragPBoard setPropertyList:currentValue forType:currentKey];
126 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
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);
144 nsCocoaUtils::CocoaPointsToDevPixels(NSMakePoint(screenPoint.x,
148 nsRefPtr<gfxASurface> surface;
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);
158 if (NS_FAILED(rv) || !surface)
161 uint32_t width = aDragRect->width;
162 uint32_t height = aDragRect->height;
164 nsRefPtr<gfxImageSurface> imgSurface = new gfxImageSurface(
165 gfxIntSize(width, height), gfxImageSurface::ImageFormatARGB32);
169 nsRefPtr<gfxContext> context = new gfxContext(imgSurface);
173 context->SetOperator(gfxContext::OPERATOR_SOURCE);
174 context->SetSource(surface);
177 uint32_t* imageData = (uint32_t*)imgSurface->Data();
178 int32_t stride = imgSurface->Stride();
180 NSBitmapImageRep* imageRep =
181 [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
188 colorSpaceName:NSDeviceRGBColorSpace
189 bytesPerRow:width * 4
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.
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);
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);
215 [[NSImage alloc] initWithSize:NSMakeSize(width / scaleFactor,
216 height / scaleFactor)];
217 [image addRepresentation:imageRep];
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.
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,
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);
248 // if no image was returned, just draw a rectangle
250 size.width = dragRect.width;
251 size.height = dragRect.height;
252 image = [[NSImage alloc] initWithSize:size];
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)];
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
288 event:mNativeDragEvent
289 pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
290 source:mNativeDragView
292 gUserCancelledDrag = false;
295 nsBaseDragService::EndDragSession(false);
299 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
303 nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex)
305 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
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));
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
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));
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?
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));
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))
367 NSString* filePath = [pFiles objectAtIndex:aItemIndex];
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);
385 nsCOMPtr<nsISupports> genericDataWrapper;
386 genericDataWrapper = do_QueryInterface(file);
387 aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
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];
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;
423 nsCOMPtr<nsISupports> genericDataWrapper;
424 nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
425 getter_AddRefs(genericDataWrapper));
426 aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
427 free(clipboardDataPtr);
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.
434 if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
435 flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime)) {
442 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
446 nsDragService::IsDataFlavorSupported(const char *aDataFlavor, bool *_retval)
448 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
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
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)
467 nsCOMPtr<nsITransferable> currentTransferable(do_QueryInterface(currentTransferableSupports));
468 if (!currentTransferable)
471 nsCOMPtr<nsISupportsArray> flavorList;
472 nsresult rv = currentTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
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));
484 nsXPIDLCString flavorStr;
485 currentFlavor->ToString(getter_Copies(flavorStr));
486 if (dataFlavor.Equals(flavorStr)) {
494 NSString *pboardType = nil;
496 if (dataFlavor.EqualsLiteral(kFileMime)) {
497 NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:NSFilenamesPboardType]];
498 if (availableType && [availableType isEqualToString:NSFilenamesPboardType])
501 else if (dataFlavor.EqualsLiteral(kURLMime)) {
502 NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:kCorePboardType_url]];
503 if (availableType && [availableType isEqualToString:kCorePboardType_url])
506 else if (nsClipboard::IsStringType(dataFlavor, &pboardType)) {
507 NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
508 if (availableType && [availableType isEqualToString:pboardType])
514 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
518 nsDragService::GetNumDropItems(uint32_t* aNumItems)
520 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
524 // first check to see if we have a number of items cached
526 mDataItems->Count(aNumItems);
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)
537 // if there is a list of files, send the number of files in that list
538 NSArray* fileNames = [globalDragPboard propertyListForType:NSFilenamesPboardType];
540 *aNumItems = [fileNames count];
544 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
548 nsDragService::EndDragSession(bool aDoneDrag)
550 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
552 if (mNativeDragView) {
553 [mNativeDragView release];
554 mNativeDragView = nil;
556 if (mNativeDragEvent) {
557 [mNativeDragEvent release];
558 mNativeDragEvent = nil;
561 mUserCancelled = gUserCancelledDrag;
563 nsresult rv = nsBaseDragService::EndDragSession(aDoneDrag);
564 mDataItems = nullptr;
567 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;