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 #include "mozilla/Logging.h"
8 #include "gfxContext.h"
9 #include "nsArrayUtils.h"
10 #include "nsDragService.h"
11 #include "nsArrayUtils.h"
12 #include "nsObjCExceptions.h"
13 #include "nsITransferable.h"
15 #include "nsClipboard.h"
18 #include "nsPrimitiveHelpers.h"
19 #include "nsLinebreakConverter.h"
23 #include "mozilla/PresShell.h"
24 #include "mozilla/dom/Document.h"
25 #include "mozilla/dom/DocumentInlines.h"
26 #include "nsIContent.h"
28 #include "nsCocoaUtils.h"
29 #include "mozilla/gfx/2D.h"
30 #include "gfxPlatform.h"
31 #include "nsDeviceContext.h"
33 using namespace mozilla;
34 using namespace mozilla::gfx;
36 extern mozilla::LazyLogModule sCocoaLog;
38 extern NSPasteboard* globalDragPboard;
39 extern ChildView* gLastDragView;
40 extern NSEvent* gLastDragMouseDownEvent;
41 extern bool gUserCancelledDrag;
43 // This global makes the transferable array available to Cocoa's promised
44 // file destination callback.
45 mozilla::StaticRefPtr<nsIArray> gDraggedTransferables;
47 nsDragService::nsDragService()
48 : mNativeDragView(nil), mNativeDragEvent(nil), mDragImageChanged(false) {}
50 nsDragService::~nsDragService() {}
52 NSImage* nsDragService::ConstructDragImage(nsINode* aDOMNode, const Maybe<CSSIntRegion>& aRegion,
53 NSPoint* aDragPoint) {
54 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
56 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
58 LayoutDeviceIntRect dragRect(0, 0, 20, 20);
59 NSImage* image = ConstructDragImage(mSourceNode, aRegion, mScreenPosition, &dragRect);
61 // if no image was returned, just draw a rectangle
63 size.width = nsCocoaUtils::DevPixelsToCocoaPoints(dragRect.width, scaleFactor);
64 size.height = nsCocoaUtils::DevPixelsToCocoaPoints(dragRect.height, scaleFactor);
65 image = [NSImage imageWithSize:size
67 drawingHandler:^BOOL(NSRect dstRect) {
68 [[NSColor grayColor] set];
69 NSBezierPath* path = [NSBezierPath bezierPathWithRect:dstRect];
70 [path setLineWidth:2.0];
76 LayoutDeviceIntPoint pt(dragRect.x, dragRect.YMost());
77 NSPoint point = nsCocoaUtils::DevPixelsToCocoaPoints(pt, scaleFactor);
78 point.y = nsCocoaUtils::FlippedScreenY(point.y);
80 point = nsCocoaUtils::ConvertPointFromScreen([mNativeDragView window], point);
81 *aDragPoint = [mNativeDragView convertPoint:point fromView:nil];
85 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
88 NSImage* nsDragService::ConstructDragImage(nsINode* aDOMNode, const Maybe<CSSIntRegion>& aRegion,
89 CSSIntPoint aPoint, LayoutDeviceIntRect* aDragRect) {
90 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
92 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
94 RefPtr<SourceSurface> surface;
96 nsresult rv = DrawDrag(aDOMNode, aRegion, aPoint, aDragRect, &surface, &pc);
97 if (pc && (!aDragRect->width || !aDragRect->height)) {
98 // just use some suitable defaults
99 int32_t size = nsCocoaUtils::CocoaPointsToDevPixels(20, scaleFactor);
100 aDragRect->SetRect(pc->CSSPixelsToDevPixels(aPoint.x), pc->CSSPixelsToDevPixels(aPoint.y), size,
104 if (NS_FAILED(rv) || !surface) return nil;
106 uint32_t width = aDragRect->width;
107 uint32_t height = aDragRect->height;
109 RefPtr<DataSourceSurface> dataSurface =
110 Factory::CreateDataSourceSurface(IntSize(width, height), SurfaceFormat::B8G8R8A8);
111 DataSourceSurface::MappedSurface map;
112 if (!dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
116 RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
117 BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride, dataSurface->GetFormat());
119 dataSurface->Unmap();
123 dt->FillRect(gfx::Rect(0, 0, width, height), SurfacePattern(surface, ExtendMode::CLAMP),
124 DrawOptions(1.0f, CompositionOp::OP_SOURCE));
126 NSBitmapImageRep* imageRep =
127 [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
134 colorSpaceName:NSDeviceRGBColorSpace
135 bytesPerRow:width * 4
138 uint8_t* dest = [imageRep bitmapData];
139 for (uint32_t i = 0; i < height; ++i) {
140 uint8_t* src = map.mData + i * map.mStride;
141 for (uint32_t j = 0; j < width; ++j) {
142 // Reduce transparency overall by multipying by a factor. Remember, Alpha
143 // is premultipled here. Also, Quartz likes RGBA, so do that translation as well.
145 dest[0] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
146 dest[1] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
147 dest[2] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
148 dest[3] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
150 dest[0] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
151 dest[1] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
152 dest[2] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
153 dest[3] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
159 dataSurface->Unmap();
162 [[NSImage alloc] initWithSize:NSMakeSize(width / scaleFactor, height / scaleFactor)];
163 [image addRepresentation:imageRep];
166 return [image autorelease];
168 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
171 nsresult nsDragService::InvokeDragSessionImpl(nsIArray* aTransferableArray,
172 const Maybe<CSSIntRegion>& aRegion,
173 uint32_t aActionType) {
174 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
177 MOZ_RELEASE_ASSERT(NS_IsMainThread());
180 if (!gLastDragView) {
181 // gLastDragView is non-null between -[ChildView mouseDown:] and -[ChildView mouseUp:].
182 // If we get here with gLastDragView being null, that means that the mouse button has already
183 // been released. In that case we need to abort the drag because the OS won't know where to drop
184 // whatever's being dragged, and we might end up with a stuck drag & drop session.
185 return NS_ERROR_FAILURE;
188 mDataItems = aTransferableArray;
190 // Save the transferables away in case a promised file callback is invoked.
191 gDraggedTransferables = aTransferableArray;
193 // We need to retain the view and the event during the drag in case either
195 mNativeDragView = [gLastDragView retain];
196 mNativeDragEvent = [gLastDragMouseDownEvent retain];
198 gUserCancelledDrag = false;
200 NSPasteboardItem* pbItem = [NSPasteboardItem new];
201 NSMutableArray* types = [NSMutableArray arrayWithCapacity:5];
203 if (gDraggedTransferables) {
205 gDraggedTransferables->GetLength(&count);
207 for (uint32_t j = 0; j < count; j++) {
208 nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(aTransferableArray, j);
209 if (!currentTransferable) {
210 return NS_ERROR_FAILURE;
213 // Transform the transferable to an NSDictionary
214 NSDictionary* pasteboardOutputDict =
215 nsClipboard::PasteboardDictFromTransferable(currentTransferable);
216 if (!pasteboardOutputDict) {
217 return NS_ERROR_FAILURE;
220 // write everything out to the general pasteboard
221 [types addObjectsFromArray:[pasteboardOutputDict allKeys]];
222 // Gecko is initiating this drag so we always want its own views to
223 // consider it. Add our wildcard type to the pasteboard to accomplish
225 [types addObject:[UTIHelper stringFromPboardType:kMozWildcardPboardType]];
228 [pbItem setDataProvider:mNativeDragView forTypes:types];
230 NSPoint draggingPoint;
231 NSImage* image = ConstructDragImage(mSourceNode, aRegion, &draggingPoint);
233 NSRect localDragRect = image.alignmentRect;
234 localDragRect.origin.x = draggingPoint.x;
235 localDragRect.origin.y = draggingPoint.y - localDragRect.size.height;
237 NSDraggingItem* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pbItem];
239 [dragItem setDraggingFrame:localDragRect contents:image];
241 nsBaseDragService::StartDragSession();
242 nsBaseDragService::OpenDragPopup();
244 NSDraggingSession* draggingSession = [mNativeDragView
245 beginDraggingSessionWithItems:[NSArray arrayWithObject:[dragItem autorelease]]
246 event:mNativeDragEvent
247 source:mNativeDragView];
248 draggingSession.animatesToStartingPositionsOnCancelOrFail =
249 !mDataTransfer || mDataTransfer->MozShowFailAnimation();
253 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
257 nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) {
258 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
260 if (!aTransferable) {
261 return NS_ERROR_FAILURE;
264 // get flavor list that includes all acceptable flavors (including ones obtained through
266 nsTArray<nsCString> flavors;
267 nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
269 return NS_ERROR_FAILURE;
272 // if this drag originated within Mozilla we should just use the cached data from
273 // when the drag started if possible
275 nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(mDataItems, aItemIndex);
276 if (currentTransferable) {
277 for (uint32_t i = 0; i < flavors.Length(); i++) {
278 nsCString& flavorStr = flavors[i];
280 nsCOMPtr<nsISupports> dataSupports;
281 rv = currentTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(dataSupports));
282 if (NS_SUCCEEDED(rv)) {
283 aTransferable->SetTransferData(flavorStr.get(), dataSupports);
284 return NS_OK; // maybe try to fill in more types? Is there a point?
290 NSArray* droppedItems = [globalDragPboard pasteboardItems];
292 return NS_ERROR_FAILURE;
295 uint32_t itemCount = [droppedItems count];
296 if (aItemIndex >= itemCount) {
297 return NS_ERROR_FAILURE;
300 NSPasteboardItem* item = [droppedItems objectAtIndex:aItemIndex];
302 return NS_ERROR_FAILURE;
305 // now check the actual clipboard for data
306 for (uint32_t i = 0; i < flavors.Length(); i++) {
307 nsCocoaUtils::SetTransferDataForTypeFromPasteboardItem(aTransferable, flavors[i], item);
312 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
316 nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
317 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
321 if (!globalDragPboard) return NS_ERROR_FAILURE;
323 nsDependentCString dataFlavor(aDataFlavor);
325 // first see if we have data for this in our cached transferable
327 uint32_t dataItemsCount;
328 mDataItems->GetLength(&dataItemsCount);
329 for (unsigned int i = 0; i < dataItemsCount; i++) {
330 nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(mDataItems, i);
331 if (!currentTransferable) continue;
333 nsTArray<nsCString> flavors;
334 nsresult rv = currentTransferable->FlavorsTransferableCanImport(flavors);
335 if (NS_FAILED(rv)) continue;
337 for (uint32_t j = 0; j < flavors.Length(); j++) {
338 if (dataFlavor.Equals(flavors[j])) {
346 const NSString* type = nil;
347 bool allowFileURL = false;
348 if (dataFlavor.EqualsLiteral(kFileMime)) {
349 type = [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL];
351 } else if (dataFlavor.EqualsLiteral(kTextMime)) {
352 type = [UTIHelper stringFromPboardType:NSPasteboardTypeString];
353 } else if (dataFlavor.EqualsLiteral(kHTMLMime)) {
354 type = [UTIHelper stringFromPboardType:NSPasteboardTypeHTML];
355 } else if (dataFlavor.EqualsLiteral(kURLMime) || dataFlavor.EqualsLiteral(kURLDataMime)) {
356 type = [UTIHelper stringFromPboardType:kPublicUrlPboardType];
357 } else if (dataFlavor.EqualsLiteral(kURLDescriptionMime)) {
358 type = [UTIHelper stringFromPboardType:kPublicUrlNamePboardType];
359 } else if (dataFlavor.EqualsLiteral(kRTFMime)) {
360 type = [UTIHelper stringFromPboardType:NSPasteboardTypeRTF];
361 } else if (dataFlavor.EqualsLiteral(kCustomTypesMime)) {
362 type = [UTIHelper stringFromPboardType:kMozCustomTypesPboardType];
365 NSString* availableType =
366 [globalDragPboard availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]];
367 if (availableType && nsCocoaUtils::IsValidPasteboardType(availableType, allowFileURL)) {
373 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
377 nsDragService::GetNumDropItems(uint32_t* aNumItems) {
378 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
382 // first check to see if we have a number of items cached
384 mDataItems->GetLength(aNumItems);
388 NSArray* droppedItems = [globalDragPboard pasteboardItems];
390 *aNumItems = [droppedItems count];
395 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
399 nsDragService::UpdateDragImage(nsINode* aImage, int32_t aImageX, int32_t aImageY) {
400 nsBaseDragService::UpdateDragImage(aImage, aImageX, aImageY);
401 mDragImageChanged = true;
405 void nsDragService::DragMovedWithView(NSDraggingSession* aSession, NSPoint aPoint) {
406 aPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y);
408 // XXX It feels like we should be using the backing scale factor at aPoint
409 // rather than the initial drag view, but I've seen no ill effects of this.
410 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
411 LayoutDeviceIntPoint devPoint = nsCocoaUtils::CocoaPointsToDevPixels(aPoint, scaleFactor);
413 // If the image has changed, call enumerateDraggingItemsWithOptions to get
414 // the item being dragged and update its image.
415 if (mDragImageChanged && mNativeDragView) {
416 mDragImageChanged = false;
418 nsPresContext* pc = nullptr;
419 nsCOMPtr<nsIContent> content = do_QueryInterface(mImage);
421 pc = content->OwnerDoc()->GetPresContext();
425 void (^changeImageBlock)(NSDraggingItem*, NSInteger, BOOL*) =
426 ^(NSDraggingItem* draggingItem, NSInteger idx, BOOL* stop) {
427 // We never add more than one item right now, but check just in case.
433 LayoutDevicePixel::ToAppUnits(devPoint, pc->DeviceContext()->AppUnitsPerDevPixel());
434 CSSIntPoint screenPoint = CSSIntPoint(nsPresContext::AppUnitsToIntCSSPixels(pt.x),
435 nsPresContext::AppUnitsToIntCSSPixels(pt.y));
437 // Create a new image; if one isn't returned don't change the current one.
438 LayoutDeviceIntRect newRect;
439 NSImage* image = ConstructDragImage(mSourceNode, Nothing(), screenPoint, &newRect);
441 NSRect draggingRect = nsCocoaUtils::GeckoRectToCocoaRectDevPix(newRect, scaleFactor);
442 [draggingItem setDraggingFrame:draggingRect contents:image];
446 [aSession enumerateDraggingItemsWithOptions:NSDraggingItemEnumerationConcurrent
448 classes:[NSArray arrayWithObject:[NSPasteboardItem class]]
450 usingBlock:changeImageBlock];
454 DragMoved(devPoint.x, devPoint.y);
458 nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
459 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
461 if (mNativeDragView) {
462 [mNativeDragView release];
463 mNativeDragView = nil;
465 if (mNativeDragEvent) {
466 [mNativeDragEvent release];
467 mNativeDragEvent = nil;
470 mUserCancelled = gUserCancelledDrag;
472 nsresult rv = nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
473 mDataItems = nullptr;
476 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);