Bug 1793629 - Implement attention indicator for the unified extensions button, r...
[gecko.git] / widget / cocoa / nsDragService.mm
blob415b8510254b8657ef8d7f062127f8849e09c1bd
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"
14 #include "nsString.h"
15 #include "nsClipboard.h"
16 #include "nsXPCOM.h"
17 #include "nsCOMPtr.h"
18 #include "nsPrimitiveHelpers.h"
19 #include "nsLinebreakConverter.h"
20 #include "nsINode.h"
21 #include "nsRect.h"
22 #include "nsPoint.h"
23 #include "mozilla/PresShell.h"
24 #include "mozilla/dom/Document.h"
25 #include "mozilla/dom/DocumentInlines.h"
26 #include "nsIContent.h"
27 #include "nsView.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 NSString* const kPublicUrlPboardType = @"public.url";
48 NSString* const kPublicUrlNamePboardType = @"public.url-name";
49 NSString* const kUrlsWithTitlesPboardType = @"WebURLsWithTitlesPboardType";
50 NSString* const kMozWildcardPboardType = @"org.mozilla.MozillaWildcard";
51 NSString* const kMozCustomTypesPboardType = @"org.mozilla.custom-clipdata";
52 NSString* const kMozFileUrlsPboardType = @"org.mozilla.file-urls";
54 nsDragService::nsDragService()
55     : mNativeDragView(nil), mNativeDragEvent(nil), mDragImageChanged(false) {}
57 nsDragService::~nsDragService() {}
59 NSImage* nsDragService::ConstructDragImage(nsINode* aDOMNode, const Maybe<CSSIntRegion>& aRegion,
60                                            NSPoint* aDragPoint) {
61   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
63   CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
65   LayoutDeviceIntRect dragRect(0, 0, 20, 20);
66   NSImage* image = ConstructDragImage(mSourceNode, aRegion, mScreenPosition, &dragRect);
67   if (!image) {
68     // if no image was returned, just draw a rectangle
69     NSSize size;
70     size.width = nsCocoaUtils::DevPixelsToCocoaPoints(dragRect.width, scaleFactor);
71     size.height = nsCocoaUtils::DevPixelsToCocoaPoints(dragRect.height, scaleFactor);
72     image = [NSImage imageWithSize:size
73                            flipped:YES
74                     drawingHandler:^BOOL(NSRect dstRect) {
75                       [[NSColor grayColor] set];
76                       NSBezierPath* path = [NSBezierPath bezierPathWithRect:dstRect];
77                       [path setLineWidth:2.0];
78                       [path stroke];
79                       return YES;
80                     }];
81   }
83   LayoutDeviceIntPoint pt(dragRect.x, dragRect.YMost());
84   NSPoint point = nsCocoaUtils::DevPixelsToCocoaPoints(pt, scaleFactor);
85   point.y = nsCocoaUtils::FlippedScreenY(point.y);
87   point = nsCocoaUtils::ConvertPointFromScreen([mNativeDragView window], point);
88   *aDragPoint = [mNativeDragView convertPoint:point fromView:nil];
90   return image;
92   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
95 NSImage* nsDragService::ConstructDragImage(nsINode* aDOMNode, const Maybe<CSSIntRegion>& aRegion,
96                                            CSSIntPoint aPoint, LayoutDeviceIntRect* aDragRect) {
97   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
99   CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
101   RefPtr<SourceSurface> surface;
102   nsPresContext* pc;
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), pc->CSSPixelsToDevPixels(aPoint.y), size,
108                        size);
109   }
111   if (NS_FAILED(rv) || !surface) return nil;
113   uint32_t width = aDragRect->width;
114   uint32_t height = aDragRect->height;
116   RefPtr<DataSourceSurface> dataSurface =
117       Factory::CreateDataSourceSurface(IntSize(width, height), SurfaceFormat::B8G8R8A8);
118   DataSourceSurface::MappedSurface map;
119   if (!dataSurface->Map(DataSourceSurface::MapType::READ_WRITE, &map)) {
120     return nil;
121   }
123   RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
124       BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride, dataSurface->GetFormat());
125   if (!dt) {
126     dataSurface->Unmap();
127     return nil;
128   }
130   dt->FillRect(gfx::Rect(0, 0, width, height), SurfacePattern(surface, ExtendMode::CLAMP),
131                DrawOptions(1.0f, CompositionOp::OP_SOURCE));
133   NSBitmapImageRep* imageRep =
134       [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
135                                               pixelsWide:width
136                                               pixelsHigh:height
137                                            bitsPerSample:8
138                                          samplesPerPixel:4
139                                                 hasAlpha:YES
140                                                 isPlanar:NO
141                                           colorSpaceName:NSDeviceRGBColorSpace
142                                              bytesPerRow:width * 4
143                                             bitsPerPixel:32];
145   uint8_t* dest = [imageRep bitmapData];
146   for (uint32_t i = 0; i < height; ++i) {
147     uint8_t* src = map.mData + i * map.mStride;
148     for (uint32_t j = 0; j < width; ++j) {
149       // Reduce transparency overall by multipying by a factor. Remember, Alpha
150       // is premultipled here. Also, Quartz likes RGBA, so do that translation as well.
151 #ifdef IS_BIG_ENDIAN
152       dest[0] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
153       dest[1] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
154       dest[2] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
155       dest[3] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
156 #else
157       dest[0] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
158       dest[1] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
159       dest[2] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
160       dest[3] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
161 #endif
162       src += 4;
163       dest += 4;
164     }
165   }
166   dataSurface->Unmap();
168   NSImage* image =
169       [[NSImage alloc] initWithSize:NSMakeSize(width / scaleFactor, height / scaleFactor)];
170   [image addRepresentation:imageRep];
171   [imageRep release];
173   return [image autorelease];
175   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
178 bool nsDragService::IsValidType(NSString* availableType, bool allowFileURL) {
179   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
181   // Prevent exposing fileURL for non-fileURL type.
182   // We need URL provided by dropped webloc file, but don't need file's URL.
183   // kUTTypeFileURL is returned by [NSPasteboard availableTypeFromArray:] for
184   // kPublicUrlPboardType, since it conforms to kPublicUrlPboardType.
185   bool isValid = true;
186   if (!allowFileURL &&
187       [availableType isEqualToString:[UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL]]) {
188     isValid = false;
189   }
191   return isValid;
193   NS_OBJC_END_TRY_BLOCK_RETURN(false);
196 NSString* nsDragService::GetStringForType(NSPasteboardItem* item, const NSString* type,
197                                           bool allowFileURL) {
198   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
200   NSString* availableType = [item availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]];
201   if (availableType && IsValidType(availableType, allowFileURL)) {
202     return [item stringForType:(id)availableType];
203   }
205   return nil;
207   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
210 NSString* nsDragService::GetTitleForURL(NSPasteboardItem* item) {
211   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
213   NSString* name =
214       GetStringForType(item, [UTIHelper stringFromPboardType:kPublicUrlNamePboardType]);
215   if (name) {
216     return name;
217   }
219   NSString* filePath = GetFilePath(item);
220   if (filePath) {
221     return [filePath lastPathComponent];
222   }
224   return nil;
226   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
229 NSString* nsDragService::GetFilePath(NSPasteboardItem* item) {
230   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
232   NSString* urlString =
233       GetStringForType(item, [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL], true);
234   if (urlString) {
235     NSURL* url = [NSURL URLWithString:urlString];
236     if (url) {
237       return [url path];
238     }
239   }
241   return nil;
243   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
246 nsresult nsDragService::InvokeDragSessionImpl(nsIArray* aTransferableArray,
247                                               const Maybe<CSSIntRegion>& aRegion,
248                                               uint32_t aActionType) {
249   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
251 #ifdef NIGHTLY_BUILD
252   MOZ_RELEASE_ASSERT(NS_IsMainThread());
253 #endif
255   if (!gLastDragView) {
256     // gLastDragView is non-null between -[ChildView mouseDown:] and -[ChildView mouseUp:].
257     // If we get here with gLastDragView being null, that means that the mouse button has already
258     // been released. In that case we need to abort the drag because the OS won't know where to drop
259     // whatever's being dragged, and we might end up with a stuck drag & drop session.
260     return NS_ERROR_FAILURE;
261   }
263   mDataItems = aTransferableArray;
265   // Save the transferables away in case a promised file callback is invoked.
266   gDraggedTransferables = aTransferableArray;
268   // We need to retain the view and the event during the drag in case either
269   // gets destroyed.
270   mNativeDragView = [gLastDragView retain];
271   mNativeDragEvent = [gLastDragMouseDownEvent retain];
273   gUserCancelledDrag = false;
275   NSPasteboardItem* pbItem = [NSPasteboardItem new];
276   NSMutableArray* types = [NSMutableArray arrayWithCapacity:5];
278   if (gDraggedTransferables) {
279     uint32_t count = 0;
280     gDraggedTransferables->GetLength(&count);
282     for (uint32_t j = 0; j < count; j++) {
283       nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(aTransferableArray, j);
284       if (!currentTransferable) {
285         return NS_ERROR_FAILURE;
286       }
288       // Transform the transferable to an NSDictionary
289       NSDictionary* pasteboardOutputDict =
290           nsClipboard::PasteboardDictFromTransferable(currentTransferable);
291       if (!pasteboardOutputDict) {
292         return NS_ERROR_FAILURE;
293       }
295       // write everything out to the general pasteboard
296       [types addObjectsFromArray:[pasteboardOutputDict allKeys]];
297       // Gecko is initiating this drag so we always want its own views to
298       // consider it. Add our wildcard type to the pasteboard to accomplish
299       // this.
300       [types addObject:[UTIHelper stringFromPboardType:kMozWildcardPboardType]];
301     }
302   }
303   [pbItem setDataProvider:mNativeDragView forTypes:types];
305   NSPoint draggingPoint;
306   NSImage* image = ConstructDragImage(mSourceNode, aRegion, &draggingPoint);
308   NSRect localDragRect = image.alignmentRect;
309   localDragRect.origin.x = draggingPoint.x;
310   localDragRect.origin.y = draggingPoint.y - localDragRect.size.height;
312   NSDraggingItem* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pbItem];
313   [pbItem release];
314   [dragItem setDraggingFrame:localDragRect contents:image];
316   nsBaseDragService::StartDragSession();
317   nsBaseDragService::OpenDragPopup();
319   NSDraggingSession* draggingSession = [mNativeDragView
320       beginDraggingSessionWithItems:[NSArray arrayWithObject:[dragItem autorelease]]
321                               event:mNativeDragEvent
322                              source:mNativeDragView];
323   draggingSession.animatesToStartingPositionsOnCancelOrFail = YES;
325   return NS_OK;
327   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
330 NS_IMETHODIMP
331 nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) {
332   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
334   if (!aTransferable) return NS_ERROR_FAILURE;
336   // get flavor list that includes all acceptable flavors (including ones obtained through
337   // conversion)
338   nsTArray<nsCString> flavors;
339   nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
340   if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
342   // if this drag originated within Mozilla we should just use the cached data from
343   // when the drag started if possible
344   if (mDataItems) {
345     nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(mDataItems, aItemIndex);
346     if (currentTransferable) {
347       for (uint32_t i = 0; i < flavors.Length(); i++) {
348         nsCString& flavorStr = flavors[i];
350         nsCOMPtr<nsISupports> dataSupports;
351         rv = currentTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(dataSupports));
352         if (NS_SUCCEEDED(rv)) {
353           aTransferable->SetTransferData(flavorStr.get(), dataSupports);
354           return NS_OK;  // maybe try to fill in more types? Is there a point?
355         }
356       }
357     }
358   }
360   // now check the actual clipboard for data
361   for (uint32_t i = 0; i < flavors.Length(); i++) {
362     nsCString& flavorStr = flavors[i];
364     MOZ_LOG(sCocoaLog, LogLevel::Info,
365             ("nsDragService::GetData: looking for clipboard data of type %s\n", flavorStr.get()));
367     NSArray* droppedItems = [globalDragPboard pasteboardItems];
368     if (!droppedItems) {
369       continue;
370     }
372     uint32_t itemCount = [droppedItems count];
373     if (aItemIndex >= itemCount) {
374       continue;
375     }
377     NSPasteboardItem* item = [droppedItems objectAtIndex:aItemIndex];
378     if (!item) {
379       continue;
380     }
382     if (flavorStr.EqualsLiteral(kFileMime)) {
383       NSString* filePath = GetFilePath(item);
384       if (!filePath) continue;
386       unsigned int stringLength = [filePath length];
387       unsigned int dataLength = (stringLength + 1) * sizeof(char16_t);  // in bytes
388       char16_t* clipboardDataPtr = (char16_t*)malloc(dataLength);
389       if (!clipboardDataPtr) return NS_ERROR_OUT_OF_MEMORY;
390       [filePath getCharacters:reinterpret_cast<unichar*>(clipboardDataPtr)];
391       clipboardDataPtr[stringLength] = 0;  // null terminate
393       nsCOMPtr<nsIFile> file;
394       rv = NS_NewLocalFile(nsDependentString(clipboardDataPtr), true, getter_AddRefs(file));
395       free(clipboardDataPtr);
396       if (NS_FAILED(rv)) continue;
398       aTransferable->SetTransferData(flavorStr.get(), file);
400       break;
401     } else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
402       NSString* availableType =
403           [item availableTypeFromArray:[NSArray arrayWithObject:kMozCustomTypesPboardType]];
404       if (!availableType || !IsValidType(availableType, false)) {
405         continue;
406       }
407       NSData* pasteboardData = [item dataForType:availableType];
408       if (!pasteboardData) {
409         continue;
410       }
412       unsigned int dataLength = [pasteboardData length];
413       void* clipboardDataPtr = malloc(dataLength);
414       if (!clipboardDataPtr) {
415         return NS_ERROR_OUT_OF_MEMORY;
416       }
417       [pasteboardData getBytes:clipboardDataPtr length:dataLength];
419       nsCOMPtr<nsISupports> genericDataWrapper;
420       nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtr, dataLength,
421                                                  getter_AddRefs(genericDataWrapper));
423       aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
424       free(clipboardDataPtr);
425       break;
426     }
428     NSString* pString = nil;
429     if (flavorStr.EqualsLiteral(kUnicodeMime)) {
430       pString = GetStringForType(item, [UTIHelper stringFromPboardType:NSPasteboardTypeString]);
431     } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
432       pString = GetStringForType(item, [UTIHelper stringFromPboardType:NSPasteboardTypeHTML]);
433     } else if (flavorStr.EqualsLiteral(kURLMime)) {
434       pString = GetStringForType(item, [UTIHelper stringFromPboardType:kPublicUrlPboardType]);
435       if (pString) {
436         NSString* title = GetTitleForURL(item);
437         if (!title) {
438           title = pString;
439         }
440         pString = [NSString stringWithFormat:@"%@\n%@", pString, title];
441       }
442     } else if (flavorStr.EqualsLiteral(kURLDataMime)) {
443       pString = GetStringForType(item, [UTIHelper stringFromPboardType:kPublicUrlPboardType]);
444     } else if (flavorStr.EqualsLiteral(kURLDescriptionMime)) {
445       pString = GetTitleForURL(item);
446     } else if (flavorStr.EqualsLiteral(kRTFMime)) {
447       pString = GetStringForType(item, [UTIHelper stringFromPboardType:NSPasteboardTypeRTF]);
448     }
449     if (pString) {
450       NSData* stringData;
451       if (flavorStr.EqualsLiteral(kRTFMime)) {
452         stringData = [pString dataUsingEncoding:NSASCIIStringEncoding];
453       } else {
454         stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
455       }
456       unsigned int dataLength = [stringData length];
457       void* clipboardDataPtr = malloc(dataLength);
458       if (!clipboardDataPtr) return NS_ERROR_OUT_OF_MEMORY;
459       [stringData getBytes:clipboardDataPtr length:dataLength];
461       // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
462       int32_t signedDataLength = dataLength;
463       nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr,
464                                                          &signedDataLength);
465       dataLength = signedDataLength;
467       // skip BOM (Byte Order Mark to distinguish little or big endian)
468       char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
469       if ((dataLength > 2) &&
470           ((clipboardDataPtrNoBOM[0] == 0xFEFF) || (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
471         dataLength -= sizeof(char16_t);
472         clipboardDataPtrNoBOM += 1;
473       }
475       nsCOMPtr<nsISupports> genericDataWrapper;
476       nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
477                                                  getter_AddRefs(genericDataWrapper));
478       aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
479       free(clipboardDataPtr);
480       break;
481     }
483     // We have never supported this on Mac OS X, we should someday. Normally dragging images
484     // in is accomplished with a file path drag instead of the image data itself.
485     /*
486     if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
487         flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime)) {
489     }
490     */
491   }
492   return NS_OK;
494   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
497 NS_IMETHODIMP
498 nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
499   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
501   *_retval = false;
503   if (!globalDragPboard) return NS_ERROR_FAILURE;
505   nsDependentCString dataFlavor(aDataFlavor);
507   // first see if we have data for this in our cached transferable
508   if (mDataItems) {
509     uint32_t dataItemsCount;
510     mDataItems->GetLength(&dataItemsCount);
511     for (unsigned int i = 0; i < dataItemsCount; i++) {
512       nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(mDataItems, i);
513       if (!currentTransferable) continue;
515       nsTArray<nsCString> flavors;
516       nsresult rv = currentTransferable->FlavorsTransferableCanImport(flavors);
517       if (NS_FAILED(rv)) continue;
519       for (uint32_t j = 0; j < flavors.Length(); j++) {
520         if (dataFlavor.Equals(flavors[j])) {
521           *_retval = true;
522           return NS_OK;
523         }
524       }
525     }
526   }
528   const NSString* type = nil;
529   bool allowFileURL = false;
530   if (dataFlavor.EqualsLiteral(kFileMime)) {
531     type = [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL];
532     allowFileURL = true;
533   } else if (dataFlavor.EqualsLiteral(kUnicodeMime)) {
534     type = [UTIHelper stringFromPboardType:NSPasteboardTypeString];
535   } else if (dataFlavor.EqualsLiteral(kHTMLMime)) {
536     type = [UTIHelper stringFromPboardType:NSPasteboardTypeHTML];
537   } else if (dataFlavor.EqualsLiteral(kURLMime) || dataFlavor.EqualsLiteral(kURLDataMime)) {
538     type = [UTIHelper stringFromPboardType:kPublicUrlPboardType];
539   } else if (dataFlavor.EqualsLiteral(kURLDescriptionMime)) {
540     type = [UTIHelper stringFromPboardType:kPublicUrlNamePboardType];
541   } else if (dataFlavor.EqualsLiteral(kRTFMime)) {
542     type = [UTIHelper stringFromPboardType:NSPasteboardTypeRTF];
543   } else if (dataFlavor.EqualsLiteral(kCustomTypesMime)) {
544     type = [UTIHelper stringFromPboardType:kMozCustomTypesPboardType];
545   }
547   NSString* availableType =
548       [globalDragPboard availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]];
549   if (availableType && IsValidType(availableType, allowFileURL)) {
550     *_retval = true;
551   }
553   return NS_OK;
555   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
558 NS_IMETHODIMP
559 nsDragService::GetNumDropItems(uint32_t* aNumItems) {
560   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
562   *aNumItems = 0;
564   // first check to see if we have a number of items cached
565   if (mDataItems) {
566     mDataItems->GetLength(aNumItems);
567     return NS_OK;
568   }
570   NSArray* droppedItems = [globalDragPboard pasteboardItems];
571   if (droppedItems) {
572     *aNumItems = [droppedItems count];
573   }
575   return NS_OK;
577   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
580 NS_IMETHODIMP
581 nsDragService::UpdateDragImage(nsINode* aImage, int32_t aImageX, int32_t aImageY) {
582   nsBaseDragService::UpdateDragImage(aImage, aImageX, aImageY);
583   mDragImageChanged = true;
584   return NS_OK;
587 void nsDragService::DragMovedWithView(NSDraggingSession* aSession, NSPoint aPoint) {
588   aPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y);
590   // XXX It feels like we should be using the backing scale factor at aPoint
591   // rather than the initial drag view, but I've seen no ill effects of this.
592   CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
593   LayoutDeviceIntPoint devPoint = nsCocoaUtils::CocoaPointsToDevPixels(aPoint, scaleFactor);
595   // If the image has changed, call enumerateDraggingItemsWithOptions to get
596   // the item being dragged and update its image.
597   if (mDragImageChanged && mNativeDragView) {
598     mDragImageChanged = false;
600     nsPresContext* pc = nullptr;
601     nsCOMPtr<nsIContent> content = do_QueryInterface(mImage);
602     if (content) {
603       pc = content->OwnerDoc()->GetPresContext();
604     }
606     if (pc) {
607       void (^changeImageBlock)(NSDraggingItem*, NSInteger, BOOL*) =
608           ^(NSDraggingItem* draggingItem, NSInteger idx, BOOL* stop) {
609             // We never add more than one item right now, but check just in case.
610             if (idx > 0) {
611               return;
612             }
614             nsPoint pt =
615                 LayoutDevicePixel::ToAppUnits(devPoint, pc->DeviceContext()->AppUnitsPerDevPixel());
616             CSSIntPoint screenPoint = CSSIntPoint(nsPresContext::AppUnitsToIntCSSPixels(pt.x),
617                                                   nsPresContext::AppUnitsToIntCSSPixels(pt.y));
619             // Create a new image; if one isn't returned don't change the current one.
620             LayoutDeviceIntRect newRect;
621             NSImage* image = ConstructDragImage(mSourceNode, Nothing(), screenPoint, &newRect);
622             if (image) {
623               NSRect draggingRect = nsCocoaUtils::GeckoRectToCocoaRectDevPix(newRect, scaleFactor);
624               [draggingItem setDraggingFrame:draggingRect contents:image];
625             }
626           };
628       [aSession enumerateDraggingItemsWithOptions:NSDraggingItemEnumerationConcurrent
629                                           forView:nil
630                                           classes:[NSArray arrayWithObject:[NSPasteboardItem class]]
631                                     searchOptions:@{}
632                                        usingBlock:changeImageBlock];
633     }
634   }
636   DragMoved(devPoint.x, devPoint.y);
639 NS_IMETHODIMP
640 nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
641   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
643   if (mNativeDragView) {
644     [mNativeDragView release];
645     mNativeDragView = nil;
646   }
647   if (mNativeDragEvent) {
648     [mNativeDragEvent release];
649     mNativeDragEvent = nil;
650   }
652   mUserCancelled = gUserCancelledDrag;
654   nsresult rv = nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
655   mDataItems = nullptr;
656   return rv;
658   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);