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 "nsIIOService.h"
26 #include "nsNetUtil.h"
27 #include "nsIDocument.h"
28 #include "nsIContent.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;
39 extern PRLogModuleInfo* sCocoaLog;
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;
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];
114 else if (currentKey == NSHTMLPboardType) {
115 [dragPBoard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
118 else if (currentKey == NSTIFFPboardType) {
119 [dragPBoard setData:currentValue forType:currentKey];
121 else if (currentKey == NSFilesPromisePboardType ||
122 currentKey == NSFilenamesPboardType) {
123 [dragPBoard setPropertyList:currentValue forType:currentKey];
130 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
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;
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),
162 if (NS_FAILED(rv) || !surface)
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)) {
178 RefPtr<DrawTarget> dt =
179 Factory::CreateDrawTargetForData(BackendType::CAIRO,
181 dataSurface->GetSize(),
183 dataSurface->GetFormat());
185 dataSurface->Unmap();
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
201 colorSpaceName:NSDeviceRGBColorSpace
202 bytesPerRow:width * 4
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.
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);
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);
226 dataSurface->Unmap();
229 [[NSImage alloc] initWithSize:NSMakeSize(width / scaleFactor,
230 height / scaleFactor)];
231 [image addRepresentation:imageRep];
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.
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,
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);
262 // if no image was returned, just draw a rectangle
264 size.width = dragRect.width;
265 size.height = dragRect.height;
266 image = [[NSImage alloc] initWithSize:size];
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)];
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
302 event:mNativeDragEvent
303 pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
304 source:mNativeDragView
306 gUserCancelledDrag = false;
309 nsBaseDragService::EndDragSession(false);
313 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
317 nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex)
319 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
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));
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
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));
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?
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));
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))
381 NSString* filePath = [pFiles objectAtIndex:aItemIndex];
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);
399 aTransferable->SetTransferData(flavorStr, file, dataLength);
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];
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;
435 nsCOMPtr<nsISupports> genericDataWrapper;
436 nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
437 getter_AddRefs(genericDataWrapper));
438 aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
439 free(clipboardDataPtr);
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.
446 if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
447 flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime)) {
454 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
458 nsDragService::IsDataFlavorSupported(const char *aDataFlavor, bool *_retval)
460 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
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
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)
479 nsCOMPtr<nsITransferable> currentTransferable(do_QueryInterface(currentTransferableSupports));
480 if (!currentTransferable)
483 nsCOMPtr<nsISupportsArray> flavorList;
484 nsresult rv = currentTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
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));
496 nsXPIDLCString flavorStr;
497 currentFlavor->ToString(getter_Copies(flavorStr));
498 if (dataFlavor.Equals(flavorStr)) {
506 NSString *pboardType = nil;
508 if (dataFlavor.EqualsLiteral(kFileMime)) {
509 NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:NSFilenamesPboardType]];
510 if (availableType && [availableType isEqualToString:NSFilenamesPboardType])
513 else if (dataFlavor.EqualsLiteral(kURLMime)) {
514 NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:kCorePboardType_url]];
515 if (availableType && [availableType isEqualToString:kCorePboardType_url])
518 else if (nsClipboard::IsStringType(dataFlavor, &pboardType)) {
519 NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
520 if (availableType && [availableType isEqualToString:pboardType])
526 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
530 nsDragService::GetNumDropItems(uint32_t* aNumItems)
532 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
536 // first check to see if we have a number of items cached
538 mDataItems->Count(aNumItems);
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)
549 // if there is a list of files, send the number of files in that list
550 NSArray* fileNames = [globalDragPboard propertyListForType:NSFilenamesPboardType];
552 *aNumItems = [fileNames count];
556 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
560 nsDragService::EndDragSession(bool aDoneDrag)
562 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
564 if (mNativeDragView) {
565 [mNativeDragView release];
566 mNativeDragView = nil;
568 if (mNativeDragEvent) {
569 [mNativeDragEvent release];
570 mNativeDragEvent = nil;
573 mUserCancelled = gUserCancelledDrag;
575 nsresult rv = nsBaseDragService::EndDragSession(aDoneDrag);
576 mDataItems = nullptr;
579 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;