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,
53 const Maybe<CSSIntRegion>& aRegion,
54 NSPoint* aDragPoint) {
55 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
57 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
59 LayoutDeviceIntRect dragRect(0, 0, 20, 20);
61 ConstructDragImage(mSourceNode, aRegion, mScreenPosition, &dragRect);
63 // if no image was returned, just draw a rectangle
66 nsCocoaUtils::DevPixelsToCocoaPoints(dragRect.width, scaleFactor);
68 nsCocoaUtils::DevPixelsToCocoaPoints(dragRect.height, scaleFactor);
69 image = [NSImage imageWithSize:size
71 drawingHandler:^BOOL(NSRect dstRect) {
72 [[NSColor grayColor] set];
74 [NSBezierPath bezierPathWithRect:dstRect];
75 [path setLineWidth:2.0];
81 LayoutDeviceIntPoint pt(dragRect.x, dragRect.YMost());
82 NSPoint point = nsCocoaUtils::DevPixelsToCocoaPoints(pt, scaleFactor);
83 point.y = nsCocoaUtils::FlippedScreenY(point.y);
85 point = nsCocoaUtils::ConvertPointFromScreen([mNativeDragView window], point);
86 *aDragPoint = [mNativeDragView convertPoint:point fromView:nil];
90 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
93 NSImage* nsDragService::ConstructDragImage(nsINode* aDOMNode,
94 const Maybe<CSSIntRegion>& aRegion,
96 LayoutDeviceIntRect* aDragRect) {
97 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
99 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
101 RefPtr<SourceSurface> surface;
103 nsresult rv = DrawDrag(aDOMNode, aRegion, aPoint, aDragRect, &surface, &pc);
104 if (pc && (!aDragRect->width || !aDragRect->height)) {
105 // just use some suitable defaults
106 int32_t size = nsCocoaUtils::CocoaPointsToDevPixels(20, scaleFactor);
107 aDragRect->SetRect(pc->CSSPixelsToDevPixels(aPoint.x),
108 pc->CSSPixelsToDevPixels(aPoint.y), size, size);
111 if (NS_FAILED(rv) || !surface) return nil;
113 uint32_t width = aDragRect->width;
114 uint32_t height = aDragRect->height;
116 RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface(
117 IntSize(width, height), SurfaceFormat::B8G8R8A8);
118 DataSourceSurface::MappedSurface map;
119 if (!dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
123 RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
124 BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride,
125 dataSurface->GetFormat());
127 dataSurface->Unmap();
131 dt->FillRect(gfx::Rect(0, 0, width, height),
132 SurfacePattern(surface, ExtendMode::CLAMP),
133 DrawOptions(1.0f, CompositionOp::OP_SOURCE));
135 NSBitmapImageRep* imageRep =
136 [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
143 colorSpaceName:NSDeviceRGBColorSpace
144 bytesPerRow:width * 4
147 uint8_t* dest = [imageRep bitmapData];
148 for (uint32_t i = 0; i < height; ++i) {
149 uint8_t* src = map.mData + i * map.mStride;
150 for (uint32_t j = 0; j < width; ++j) {
151 // Reduce transparency overall by multipying by a factor. Remember, Alpha
152 // is premultipled here. Also, Quartz likes RGBA, so do that translation
155 dest[0] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
156 dest[1] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
157 dest[2] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
158 dest[3] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
160 dest[0] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
161 dest[1] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
162 dest[2] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
163 dest[3] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
169 dataSurface->Unmap();
171 NSImage* image = [[NSImage alloc]
172 initWithSize:NSMakeSize(width / scaleFactor, height / scaleFactor)];
173 [image addRepresentation:imageRep];
176 return [image autorelease];
178 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
181 nsresult nsDragService::InvokeDragSessionImpl(
182 nsIArray* aTransferableArray, const Maybe<CSSIntRegion>& aRegion,
183 uint32_t aActionType) {
184 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
187 MOZ_RELEASE_ASSERT(NS_IsMainThread());
190 if (!gLastDragView) {
191 // gLastDragView is non-null between -[ChildView mouseDown:] and -[ChildView
192 // mouseUp:]. If we get here with gLastDragView being null, that means that
193 // the mouse button has already been released. In that case we need to abort
194 // the drag because the OS won't know where to drop whatever's being
195 // dragged, and we might end up with a stuck drag & drop session.
196 return NS_ERROR_FAILURE;
199 mDataItems = aTransferableArray;
201 // Save the transferables away in case a promised file callback is invoked.
202 gDraggedTransferables = aTransferableArray;
204 // We need to retain the view and the event during the drag in case either
206 mNativeDragView = [gLastDragView retain];
207 mNativeDragEvent = [gLastDragMouseDownEvent retain];
209 gUserCancelledDrag = false;
211 NSPasteboardItem* pbItem = [NSPasteboardItem new];
212 NSMutableArray* types = [NSMutableArray arrayWithCapacity:5];
214 if (gDraggedTransferables) {
216 gDraggedTransferables->GetLength(&count);
218 for (uint32_t j = 0; j < count; j++) {
219 nsCOMPtr<nsITransferable> currentTransferable =
220 do_QueryElementAt(aTransferableArray, j);
221 if (!currentTransferable) {
222 return NS_ERROR_FAILURE;
225 // Transform the transferable to an NSDictionary
226 NSDictionary* pasteboardOutputDict =
227 nsClipboard::PasteboardDictFromTransferable(currentTransferable);
228 if (!pasteboardOutputDict) {
229 return NS_ERROR_FAILURE;
232 // write everything out to the general pasteboard
233 [types addObjectsFromArray:[pasteboardOutputDict allKeys]];
234 // Gecko is initiating this drag so we always want its own views to
235 // consider it. Add our wildcard type to the pasteboard to accomplish
237 [types addObject:[UTIHelper stringFromPboardType:kMozWildcardPboardType]];
240 [pbItem setDataProvider:mNativeDragView forTypes:types];
242 NSPoint draggingPoint;
243 NSImage* image = ConstructDragImage(mSourceNode, aRegion, &draggingPoint);
245 NSRect localDragRect = image.alignmentRect;
246 localDragRect.origin.x = draggingPoint.x;
247 localDragRect.origin.y = draggingPoint.y - localDragRect.size.height;
249 NSDraggingItem* dragItem =
250 [[NSDraggingItem alloc] initWithPasteboardWriter:pbItem];
252 [dragItem setDraggingFrame:localDragRect contents:image];
254 nsBaseDragService::StartDragSession();
255 nsBaseDragService::OpenDragPopup();
257 NSDraggingSession* draggingSession = [mNativeDragView
258 beginDraggingSessionWithItems:[NSArray
259 arrayWithObject:[dragItem autorelease]]
260 event:mNativeDragEvent
261 source:mNativeDragView];
262 draggingSession.animatesToStartingPositionsOnCancelOrFail =
263 !mDataTransfer || mDataTransfer->MozShowFailAnimation();
267 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
271 nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) {
272 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
274 if (!aTransferable) {
275 return NS_ERROR_FAILURE;
278 // get flavor list that includes all acceptable flavors (including ones
279 // obtained through conversion)
280 nsTArray<nsCString> flavors;
281 nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
283 return NS_ERROR_FAILURE;
286 // if this drag originated within Mozilla we should just use the cached data
287 // from when the drag started if possible
289 nsCOMPtr<nsITransferable> currentTransferable =
290 do_QueryElementAt(mDataItems, aItemIndex);
291 if (currentTransferable) {
292 for (uint32_t i = 0; i < flavors.Length(); i++) {
293 nsCString& flavorStr = flavors[i];
295 nsCOMPtr<nsISupports> dataSupports;
296 rv = currentTransferable->GetTransferData(flavorStr.get(),
297 getter_AddRefs(dataSupports));
298 if (NS_SUCCEEDED(rv)) {
299 aTransferable->SetTransferData(flavorStr.get(), dataSupports);
300 return NS_OK; // maybe try to fill in more types? Is there a point?
306 NSArray* droppedItems = [globalDragPboard pasteboardItems];
308 return NS_ERROR_FAILURE;
311 uint32_t itemCount = [droppedItems count];
312 if (aItemIndex >= itemCount) {
313 return NS_ERROR_FAILURE;
316 NSPasteboardItem* item = [droppedItems objectAtIndex:aItemIndex];
318 return NS_ERROR_FAILURE;
321 // now check the actual clipboard for data
322 for (uint32_t i = 0; i < flavors.Length(); i++) {
323 nsCocoaUtils::SetTransferDataForTypeFromPasteboardItem(aTransferable,
329 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
333 nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
334 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
338 if (!globalDragPboard) return NS_ERROR_FAILURE;
340 nsDependentCString dataFlavor(aDataFlavor);
342 // first see if we have data for this in our cached transferable
344 uint32_t dataItemsCount;
345 mDataItems->GetLength(&dataItemsCount);
346 for (unsigned int i = 0; i < dataItemsCount; i++) {
347 nsCOMPtr<nsITransferable> currentTransferable =
348 do_QueryElementAt(mDataItems, i);
349 if (!currentTransferable) continue;
351 nsTArray<nsCString> flavors;
352 nsresult rv = currentTransferable->FlavorsTransferableCanImport(flavors);
353 if (NS_FAILED(rv)) continue;
355 for (uint32_t j = 0; j < flavors.Length(); j++) {
356 if (dataFlavor.Equals(flavors[j])) {
364 const NSString* type = nil;
365 bool allowFileURL = false;
366 if (dataFlavor.EqualsLiteral(kFileMime)) {
367 type = [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL];
369 } else if (dataFlavor.EqualsLiteral(kTextMime)) {
370 type = [UTIHelper stringFromPboardType:NSPasteboardTypeString];
371 } else if (dataFlavor.EqualsLiteral(kHTMLMime)) {
372 type = [UTIHelper stringFromPboardType:NSPasteboardTypeHTML];
373 } else if (dataFlavor.EqualsLiteral(kURLMime) ||
374 dataFlavor.EqualsLiteral(kURLDataMime)) {
375 type = [UTIHelper stringFromPboardType:kPublicUrlPboardType];
376 } else if (dataFlavor.EqualsLiteral(kURLDescriptionMime)) {
377 type = [UTIHelper stringFromPboardType:kPublicUrlNamePboardType];
378 } else if (dataFlavor.EqualsLiteral(kRTFMime)) {
379 type = [UTIHelper stringFromPboardType:NSPasteboardTypeRTF];
380 } else if (dataFlavor.EqualsLiteral(kCustomTypesMime)) {
381 type = [UTIHelper stringFromPboardType:kMozCustomTypesPboardType];
384 NSString* availableType = [globalDragPboard
385 availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]];
387 nsCocoaUtils::IsValidPasteboardType(availableType, allowFileURL)) {
393 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
397 nsDragService::GetNumDropItems(uint32_t* aNumItems) {
398 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
402 // first check to see if we have a number of items cached
404 mDataItems->GetLength(aNumItems);
408 NSArray* droppedItems = [globalDragPboard pasteboardItems];
410 *aNumItems = [droppedItems count];
415 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
419 nsDragService::UpdateDragImage(nsINode* aImage, int32_t aImageX,
421 nsBaseDragService::UpdateDragImage(aImage, aImageX, aImageY);
422 mDragImageChanged = true;
426 void nsDragService::DragMovedWithView(NSDraggingSession* aSession,
428 aPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y);
430 // XXX It feels like we should be using the backing scale factor at aPoint
431 // rather than the initial drag view, but I've seen no ill effects of this.
432 CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
433 LayoutDeviceIntPoint devPoint =
434 nsCocoaUtils::CocoaPointsToDevPixels(aPoint, scaleFactor);
436 // If the image has changed, call enumerateDraggingItemsWithOptions to get
437 // the item being dragged and update its image.
438 if (mDragImageChanged && mNativeDragView) {
439 mDragImageChanged = false;
441 nsPresContext* pc = nullptr;
442 nsCOMPtr<nsIContent> content = do_QueryInterface(mImage);
444 pc = content->OwnerDoc()->GetPresContext();
448 void (^changeImageBlock)(NSDraggingItem*, NSInteger, BOOL*) = ^(
449 NSDraggingItem* draggingItem, NSInteger idx, BOOL* stop) {
450 // We never add more than one item right now, but check just in case.
455 nsPoint pt = LayoutDevicePixel::ToAppUnits(
456 devPoint, pc->DeviceContext()->AppUnitsPerDevPixel());
457 CSSIntPoint screenPoint =
458 CSSIntPoint(nsPresContext::AppUnitsToIntCSSPixels(pt.x),
459 nsPresContext::AppUnitsToIntCSSPixels(pt.y));
461 // Create a new image; if one isn't returned don't change the current
463 LayoutDeviceIntRect newRect;
465 ConstructDragImage(mSourceNode, Nothing(), screenPoint, &newRect);
467 NSRect draggingRect =
468 nsCocoaUtils::GeckoRectToCocoaRectDevPix(newRect, scaleFactor);
469 [draggingItem setDraggingFrame:draggingRect contents:image];
474 enumerateDraggingItemsWithOptions:NSDraggingItemEnumerationConcurrent
478 [NSPasteboardItem class]]
480 usingBlock:changeImageBlock];
484 DragMoved(devPoint.x, devPoint.y);
488 nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
489 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
491 if (mNativeDragView) {
492 [mNativeDragView release];
493 mNativeDragView = nil;
495 if (mNativeDragEvent) {
496 [mNativeDragEvent release];
497 mNativeDragEvent = nil;
500 mUserCancelled = gUserCancelledDrag;
502 nsresult rv = nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
503 mDataItems = nullptr;
506 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);