Files
tubestation/widget/cocoa/nsDragService.mm
Carsten "Tomcat" Book 89becff57d Backed out 14 changesets (bug 1165515) for linux x64 e10s m2 test failures
Backed out changeset d68dcf2ef372 (bug 1165515)
Backed out changeset 7c3b45a47811 (bug 1165515)
Backed out changeset b668b617bef2 (bug 1165515)
Backed out changeset d0916e1283a2 (bug 1165515)
Backed out changeset ac4dc7489942 (bug 1165515)
Backed out changeset e9632ce8bc65 (bug 1165515)
Backed out changeset c16d215cc7e4 (bug 1165515)
Backed out changeset e4d474f3c51a (bug 1165515)
Backed out changeset d87680bf9f7c (bug 1165515)
Backed out changeset b3c0a45ba99e (bug 1165515)
Backed out changeset 9370fa197674 (bug 1165515)
Backed out changeset 50970d668ca1 (bug 1165515)
Backed out changeset ffa4eb6d24b9 (bug 1165515)
Backed out changeset 5fcf1203cc1d (bug 1165515)
2015-06-02 13:05:56 +02:00

576 lines
20 KiB
Plaintext

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/Logging.h"
#include "nsDragService.h"
#include "nsObjCExceptions.h"
#include "nsITransferable.h"
#include "nsString.h"
#include "nsClipboard.h"
#include "nsXPCOM.h"
#include "nsISupportsPrimitives.h"
#include "nsCOMPtr.h"
#include "nsPrimitiveHelpers.h"
#include "nsLinebreakConverter.h"
#include "nsIMacUtils.h"
#include "nsIDOMNode.h"
#include "nsRect.h"
#include "nsPoint.h"
#include "nsIIOService.h"
#include "nsNetUtil.h"
#include "nsIDocument.h"
#include "nsIContent.h"
#include "nsView.h"
#include "gfxContext.h"
#include "nsCocoaUtils.h"
#include "mozilla/gfx/2D.h"
#include "gfxPlatform.h"
using namespace mozilla;
using namespace mozilla::gfx;
extern PRLogModuleInfo* sCocoaLog;
extern void EnsureLogInitialized();
extern NSPasteboard* globalDragPboard;
extern NSView* gLastDragView;
extern NSEvent* gLastDragMouseDownEvent;
extern bool gUserCancelledDrag;
// This global makes the transferable array available to Cocoa's promised
// file destination callback.
nsISupportsArray *gDraggedTransferables = nullptr;
NSString* const kWildcardPboardType = @"MozillaWildcard";
NSString* const kCorePboardType_url = @"CorePasteboardFlavorType 0x75726C20"; // 'url ' url
NSString* const kCorePboardType_urld = @"CorePasteboardFlavorType 0x75726C64"; // 'urld' desc
NSString* const kCorePboardType_urln = @"CorePasteboardFlavorType 0x75726C6E"; // 'urln' title
nsDragService::nsDragService()
{
mNativeDragView = nil;
mNativeDragEvent = nil;
EnsureLogInitialized();
}
nsDragService::~nsDragService()
{
}
static nsresult SetUpDragClipboard(nsISupportsArray* aTransferableArray)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
if (!aTransferableArray)
return NS_ERROR_FAILURE;
uint32_t count = 0;
aTransferableArray->Count(&count);
NSPasteboard* dragPBoard = [NSPasteboard pasteboardWithName:NSDragPboard];
for (uint32_t i = 0; i < count; i++) {
nsCOMPtr<nsISupports> currentTransferableSupports;
aTransferableArray->GetElementAt(i, getter_AddRefs(currentTransferableSupports));
if (!currentTransferableSupports)
return NS_ERROR_FAILURE;
nsCOMPtr<nsITransferable> currentTransferable(do_QueryInterface(currentTransferableSupports));
if (!currentTransferable)
return NS_ERROR_FAILURE;
// Transform the transferable to an NSDictionary
NSDictionary* pasteboardOutputDict = nsClipboard::PasteboardDictFromTransferable(currentTransferable);
if (!pasteboardOutputDict)
return NS_ERROR_FAILURE;
// write everything out to the general pasteboard
unsigned int typeCount = [pasteboardOutputDict count];
NSMutableArray* types = [NSMutableArray arrayWithCapacity:typeCount + 1];
[types addObjectsFromArray:[pasteboardOutputDict allKeys]];
// Gecko is initiating this drag so we always want its own views to consider
// it. Add our wildcard type to the pasteboard to accomplish this.
[types addObject:kWildcardPboardType]; // we don't increase the count for the loop below on purpose
[dragPBoard declareTypes:types owner:nil];
for (unsigned int i = 0; i < typeCount; i++) {
NSString* currentKey = [types objectAtIndex:i];
id currentValue = [pasteboardOutputDict valueForKey:currentKey];
if (currentKey == NSStringPboardType ||
currentKey == kCorePboardType_url ||
currentKey == kCorePboardType_urld ||
currentKey == kCorePboardType_urln) {
[dragPBoard setString:currentValue forType:currentKey];
}
else if (currentKey == NSHTMLPboardType) {
[dragPBoard setString:(nsClipboard::WrapHtmlForSystemPasteboard(currentValue))
forType:currentKey];
}
else if (currentKey == NSTIFFPboardType) {
[dragPBoard setData:currentValue forType:currentKey];
}
else if (currentKey == NSFilesPromisePboardType ||
currentKey == NSFilenamesPboardType) {
[dragPBoard setPropertyList:currentValue forType:currentKey];
}
}
}
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
NSImage*
nsDragService::ConstructDragImage(nsIDOMNode* aDOMNode,
nsIntRect* aDragRect,
nsIScriptableRegion* aRegion)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
NSPoint screenPoint =
[[gLastDragView window] convertBaseToScreen:
[gLastDragMouseDownEvent locationInWindow]];
// Y coordinates are bottom to top, so reverse this
screenPoint.y = nsCocoaUtils::FlippedScreenY(screenPoint.y);
CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(gLastDragView);
RefPtr<SourceSurface> surface;
nsPresContext* pc;
nsresult rv = DrawDrag(aDOMNode, aRegion,
NSToIntRound(screenPoint.x),
NSToIntRound(screenPoint.y),
aDragRect, &surface, &pc);
if (!aDragRect->width || !aDragRect->height) {
// just use some suitable defaults
int32_t size = nsCocoaUtils::CocoaPointsToDevPixels(20, scaleFactor);
aDragRect->SetRect(nsCocoaUtils::CocoaPointsToDevPixels(screenPoint.x, scaleFactor),
nsCocoaUtils::CocoaPointsToDevPixels(screenPoint.y, scaleFactor),
size, size);
}
if (NS_FAILED(rv) || !surface)
return nil;
uint32_t width = aDragRect->width;
uint32_t height = aDragRect->height;
RefPtr<DataSourceSurface> dataSurface =
Factory::CreateDataSourceSurface(IntSize(width, height),
SurfaceFormat::B8G8R8A8);
DataSourceSurface::MappedSurface map;
if (!dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
return nil;
}
RefPtr<DrawTarget> dt =
Factory::CreateDrawTargetForData(BackendType::CAIRO,
map.mData,
dataSurface->GetSize(),
map.mStride,
dataSurface->GetFormat());
if (!dt) {
dataSurface->Unmap();
return nil;
}
dt->FillRect(gfx::Rect(0, 0, width, height),
SurfacePattern(surface, ExtendMode::CLAMP),
DrawOptions(1.0f, CompositionOp::OP_SOURCE));
NSBitmapImageRep* imageRep =
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:width
pixelsHigh:height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:width * 4
bitsPerPixel:32];
uint8_t* dest = [imageRep bitmapData];
for (uint32_t i = 0; i < height; ++i) {
uint8_t* src = map.mData + i * map.mStride;
for (uint32_t j = 0; j < width; ++j) {
// Reduce transparency overall by multipying by a factor. Remember, Alpha
// is premultipled here. Also, Quartz likes RGBA, so do that translation as well.
#ifdef IS_BIG_ENDIAN
dest[0] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
dest[1] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
dest[2] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
dest[3] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
#else
dest[0] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
dest[1] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
dest[2] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
dest[3] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
#endif
src += 4;
dest += 4;
}
}
dataSurface->Unmap();
NSImage* image =
[[NSImage alloc] initWithSize:NSMakeSize(width / scaleFactor,
height / scaleFactor)];
[image addRepresentation:imageRep];
[imageRep release];
return [image autorelease];
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
}
// We can only invoke NSView's 'dragImage:at:offset:event:pasteboard:source:slideBack:' from
// within NSView's 'mouseDown:' or 'mouseDragged:'. Luckily 'mouseDragged' is always on the
// stack when InvokeDragSession gets called.
NS_IMETHODIMP
nsDragService::InvokeDragSession(nsIDOMNode* aDOMNode, nsISupportsArray* aTransferableArray,
nsIScriptableRegion* aDragRgn, uint32_t aActionType)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
nsresult rv = nsBaseDragService::InvokeDragSession(aDOMNode,
aTransferableArray,
aDragRgn, aActionType);
NS_ENSURE_SUCCESS(rv, rv);
mDataItems = aTransferableArray;
// put data on the clipboard
if (NS_FAILED(SetUpDragClipboard(aTransferableArray)))
return NS_ERROR_FAILURE;
nsIntRect dragRect(0, 0, 20, 20);
NSImage* image = ConstructDragImage(aDOMNode, &dragRect, aDragRgn);
if (!image) {
// if no image was returned, just draw a rectangle
NSSize size;
size.width = dragRect.width;
size.height = dragRect.height;
image = [[NSImage alloc] initWithSize:size];
[image lockFocus];
[[NSColor grayColor] set];
NSBezierPath* path = [NSBezierPath bezierPath];
[path setLineWidth:2.0];
[path moveToPoint:NSMakePoint(0, 0)];
[path lineToPoint:NSMakePoint(0, size.height)];
[path lineToPoint:NSMakePoint(size.width, size.height)];
[path lineToPoint:NSMakePoint(size.width, 0)];
[path lineToPoint:NSMakePoint(0, 0)];
[path stroke];
[image unlockFocus];
}
LayoutDeviceIntPoint pt(dragRect.x, dragRect.YMost());
CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(gLastDragView);
NSPoint point = nsCocoaUtils::DevPixelsToCocoaPoints(pt, scaleFactor);
point.y = nsCocoaUtils::FlippedScreenY(point.y);
point = [[gLastDragView window] convertScreenToBase: point];
NSPoint localPoint = [gLastDragView convertPoint:point fromView:nil];
// Save the transferables away in case a promised file callback is invoked.
gDraggedTransferables = aTransferableArray;
nsBaseDragService::StartDragSession();
nsBaseDragService::OpenDragPopup();
// We need to retain the view and the event during the drag in case either gets destroyed.
mNativeDragView = [gLastDragView retain];
mNativeDragEvent = [gLastDragMouseDownEvent retain];
gUserCancelledDrag = false;
[mNativeDragView dragImage:image
at:localPoint
offset:NSZeroSize
event:mNativeDragEvent
pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
source:mNativeDragView
slideBack:YES];
gUserCancelledDrag = false;
if (mDoingDrag)
nsBaseDragService::EndDragSession(false);
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
NS_IMETHODIMP
nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
if (!aTransferable)
return NS_ERROR_FAILURE;
// get flavor list that includes all acceptable flavors (including ones obtained through conversion)
nsCOMPtr<nsISupportsArray> flavorList;
nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
if (NS_FAILED(rv))
return NS_ERROR_FAILURE;
uint32_t acceptableFlavorCount;
flavorList->Count(&acceptableFlavorCount);
// if this drag originated within Mozilla we should just use the cached data from
// when the drag started if possible
if (mDataItems) {
nsCOMPtr<nsISupports> currentTransferableSupports;
mDataItems->GetElementAt(aItemIndex, getter_AddRefs(currentTransferableSupports));
if (currentTransferableSupports) {
nsCOMPtr<nsITransferable> currentTransferable(do_QueryInterface(currentTransferableSupports));
if (currentTransferable) {
for (uint32_t i = 0; i < acceptableFlavorCount; i++) {
nsCOMPtr<nsISupports> genericFlavor;
flavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
if (!currentFlavor)
continue;
nsXPIDLCString flavorStr;
currentFlavor->ToString(getter_Copies(flavorStr));
nsCOMPtr<nsISupports> dataSupports;
uint32_t dataSize = 0;
rv = currentTransferable->GetTransferData(flavorStr, getter_AddRefs(dataSupports), &dataSize);
if (NS_SUCCEEDED(rv)) {
aTransferable->SetTransferData(flavorStr, dataSupports, dataSize);
return NS_OK; // maybe try to fill in more types? Is there a point?
}
}
}
}
}
// now check the actual clipboard for data
for (uint32_t i = 0; i < acceptableFlavorCount; i++) {
nsCOMPtr<nsISupports> genericFlavor;
flavorList->GetElementAt(i, getter_AddRefs(genericFlavor));
nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
if (!currentFlavor)
continue;
nsXPIDLCString flavorStr;
currentFlavor->ToString(getter_Copies(flavorStr));
MOZ_LOG(sCocoaLog, PR_LOG_ALWAYS, ("nsDragService::GetData: looking for clipboard data of type %s\n", flavorStr.get()));
if (flavorStr.EqualsLiteral(kFileMime)) {
NSArray* pFiles = [globalDragPboard propertyListForType:NSFilenamesPboardType];
if (!pFiles || [pFiles count] < (aItemIndex + 1))
continue;
NSString* filePath = [pFiles objectAtIndex:aItemIndex];
if (!filePath)
continue;
unsigned int stringLength = [filePath length];
unsigned int dataLength = (stringLength + 1) * sizeof(char16_t); // in bytes
char16_t* clipboardDataPtr = (char16_t*)malloc(dataLength);
if (!clipboardDataPtr)
return NS_ERROR_OUT_OF_MEMORY;
[filePath getCharacters:reinterpret_cast<unichar*>(clipboardDataPtr)];
clipboardDataPtr[stringLength] = 0; // null terminate
nsCOMPtr<nsIFile> file;
nsresult rv = NS_NewLocalFile(nsDependentString(clipboardDataPtr), true, getter_AddRefs(file));
free(clipboardDataPtr);
if (NS_FAILED(rv))
continue;
aTransferable->SetTransferData(flavorStr, file, dataLength);
break;
}
NSString *pboardType = NSStringPboardType;
if (nsClipboard::IsStringType(flavorStr, &pboardType) ||
flavorStr.EqualsLiteral(kURLMime) ||
flavorStr.EqualsLiteral(kURLDataMime) ||
flavorStr.EqualsLiteral(kURLDescriptionMime)) {
NSString* pString = [globalDragPboard stringForType:pboardType];
if (!pString)
continue;
NSData* stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
unsigned int dataLength = [stringData length];
void* clipboardDataPtr = malloc(dataLength);
if (!clipboardDataPtr)
return NS_ERROR_OUT_OF_MEMORY;
[stringData getBytes:clipboardDataPtr];
// The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
int32_t signedDataLength = dataLength;
nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr, &signedDataLength);
dataLength = signedDataLength;
// skip BOM (Byte Order Mark to distinguish little or big endian)
char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
if ((dataLength > 2) &&
((clipboardDataPtrNoBOM[0] == 0xFEFF) ||
(clipboardDataPtrNoBOM[0] == 0xFFFE))) {
dataLength -= sizeof(char16_t);
clipboardDataPtrNoBOM += 1;
}
nsCOMPtr<nsISupports> genericDataWrapper;
nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
getter_AddRefs(genericDataWrapper));
aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLength);
free(clipboardDataPtr);
break;
}
// We have never supported this on Mac OS X, we should someday. Normally dragging images
// in is accomplished with a file path drag instead of the image data itself.
/*
if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime)) {
}
*/
}
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
NS_IMETHODIMP
nsDragService::IsDataFlavorSupported(const char *aDataFlavor, bool *_retval)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
*_retval = false;
if (!globalDragPboard)
return NS_ERROR_FAILURE;
nsDependentCString dataFlavor(aDataFlavor);
// first see if we have data for this in our cached transferable
if (mDataItems) {
uint32_t dataItemsCount;
mDataItems->Count(&dataItemsCount);
for (unsigned int i = 0; i < dataItemsCount; i++) {
nsCOMPtr<nsISupports> currentTransferableSupports;
mDataItems->GetElementAt(i, getter_AddRefs(currentTransferableSupports));
if (!currentTransferableSupports)
continue;
nsCOMPtr<nsITransferable> currentTransferable(do_QueryInterface(currentTransferableSupports));
if (!currentTransferable)
continue;
nsCOMPtr<nsISupportsArray> flavorList;
nsresult rv = currentTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
if (NS_FAILED(rv))
continue;
uint32_t flavorCount;
flavorList->Count(&flavorCount);
for (uint32_t j = 0; j < flavorCount; j++) {
nsCOMPtr<nsISupports> genericFlavor;
flavorList->GetElementAt(j, getter_AddRefs(genericFlavor));
nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor));
if (!currentFlavor)
continue;
nsXPIDLCString flavorStr;
currentFlavor->ToString(getter_Copies(flavorStr));
if (dataFlavor.Equals(flavorStr)) {
*_retval = true;
return NS_OK;
}
}
}
}
NSString *pboardType = nil;
if (dataFlavor.EqualsLiteral(kFileMime)) {
NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:NSFilenamesPboardType]];
if (availableType && [availableType isEqualToString:NSFilenamesPboardType])
*_retval = true;
}
else if (dataFlavor.EqualsLiteral(kURLMime)) {
NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:kCorePboardType_url]];
if (availableType && [availableType isEqualToString:kCorePboardType_url])
*_retval = true;
}
else if (nsClipboard::IsStringType(dataFlavor, &pboardType)) {
NSString* availableType = [globalDragPboard availableTypeFromArray:[NSArray arrayWithObject:pboardType]];
if (availableType && [availableType isEqualToString:pboardType])
*_retval = true;
}
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
NS_IMETHODIMP
nsDragService::GetNumDropItems(uint32_t* aNumItems)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
*aNumItems = 0;
// first check to see if we have a number of items cached
if (mDataItems) {
mDataItems->Count(aNumItems);
return NS_OK;
}
// if there is a clipboard and there is something on it, then there is at least 1 item
NSArray* clipboardTypes = [globalDragPboard types];
if (globalDragPboard && [clipboardTypes count] > 0)
*aNumItems = 1;
else
return NS_OK;
// if there is a list of files, send the number of files in that list
NSArray* fileNames = [globalDragPboard propertyListForType:NSFilenamesPboardType];
if (fileNames)
*aNumItems = [fileNames count];
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
NS_IMETHODIMP
nsDragService::EndDragSession(bool aDoneDrag)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
if (mNativeDragView) {
[mNativeDragView release];
mNativeDragView = nil;
}
if (mNativeDragEvent) {
[mNativeDragEvent release];
mNativeDragEvent = nil;
}
mUserCancelled = gUserCancelledDrag;
nsresult rv = nsBaseDragService::EndDragSession(aDoneDrag);
mDataItems = nullptr;
return rv;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}