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/. */
5
6#include "mozilla/Logging.h"
7
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"
32
33using namespace mozilla;
34using namespace mozilla::gfx;
35
36extern mozilla::LazyLogModule sCocoaLog;
37
38extern NSPasteboard* globalDragPboard;
39extern ChildView* gLastDragView;
40extern NSEvent* gLastDragMouseDownEvent;
41extern bool gUserCancelledDrag;
42
43// This global makes the transferable array available to Cocoa's promised
44// file destination callback.
45nsIArray* gDraggedTransferables = nullptr;
46
47NSString* const kPublicUrlPboardType = @"public.url";
48NSString* const kPublicUrlNamePboardType = @"public.url-name";
49NSString* const kUrlsWithTitlesPboardType = @"WebURLsWithTitlesPboardType";
50NSString* const kMozWildcardPboardType = @"org.mozilla.MozillaWildcard";
51NSString* const kMozCustomTypesPboardType = @"org.mozilla.custom-clipdata";
52NSString* const kMozFileUrlsPboardType = @"org.mozilla.file-urls";
53
54nsDragService::nsDragService()
55    : mNativeDragView(nil), mNativeDragEvent(nil), mDragImageChanged(false) {}
56
57nsDragService::~nsDragService() {}
58
59NSImage* nsDragService::ConstructDragImage(nsINode* aDOMNode, const Maybe<CSSIntRegion>& aRegion,
60                                           NSPoint* aDragPoint) {
61  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
62
63  CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
64
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  }
82
83  LayoutDeviceIntPoint pt(dragRect.x, dragRect.YMost());
84  NSPoint point = nsCocoaUtils::DevPixelsToCocoaPoints(pt, scaleFactor);
85  point.y = nsCocoaUtils::FlippedScreenY(point.y);
86
87  point = nsCocoaUtils::ConvertPointFromScreen([mNativeDragView window], point);
88  *aDragPoint = [mNativeDragView convertPoint:point fromView:nil];
89
90  return image;
91
92  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
93}
94
95NSImage* nsDragService::ConstructDragImage(nsINode* aDOMNode, const Maybe<CSSIntRegion>& aRegion,
96                                           CSSIntPoint aPoint, LayoutDeviceIntRect* aDragRect) {
97  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
98
99  CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
100
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  }
110
111  if (NS_FAILED(rv) || !surface) return nil;
112
113  uint32_t width = aDragRect->width;
114  uint32_t height = aDragRect->height;
115
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  }
122
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  }
129
130  dt->FillRect(gfx::Rect(0, 0, width, height), SurfacePattern(surface, ExtendMode::CLAMP),
131               DrawOptions(1.0f, CompositionOp::OP_SOURCE));
132
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];
144
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();
167
168  NSImage* image =
169      [[NSImage alloc] initWithSize:NSMakeSize(width / scaleFactor, height / scaleFactor)];
170  [image addRepresentation:imageRep];
171  [imageRep release];
172
173  return [image autorelease];
174
175  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
176}
177
178bool nsDragService::IsValidType(NSString* availableType, bool allowFileURL) {
179  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
180
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  }
190
191  return isValid;
192
193  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(false);
194}
195
196NSString* nsDragService::GetStringForType(NSPasteboardItem* item, const NSString* type,
197                                          bool allowFileURL) {
198  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
199
200  NSString* availableType = [item availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]];
201  if (availableType && IsValidType(availableType, allowFileURL)) {
202    return [item stringForType:(id)availableType];
203  }
204
205  return nil;
206
207  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
208}
209
210NSString* nsDragService::GetTitleForURL(NSPasteboardItem* item) {
211  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
212
213  NSString* name =
214      GetStringForType(item, [UTIHelper stringFromPboardType:kPublicUrlNamePboardType]);
215  if (name) {
216    return name;
217  }
218
219  NSString* filePath = GetFilePath(item);
220  if (filePath) {
221    return [filePath lastPathComponent];
222  }
223
224  return nil;
225
226  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
227}
228
229NSString* nsDragService::GetFilePath(NSPasteboardItem* item) {
230  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
231
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  }
240
241  return nil;
242
243  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
244}
245
246nsresult nsDragService::InvokeDragSessionImpl(nsIArray* aTransferableArray,
247                                              const Maybe<CSSIntRegion>& aRegion,
248                                              uint32_t aActionType) {
249  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
250
251  if (!gLastDragView) {
252    // gLastDragView is non-null between -[ChildView mouseDown:] and -[ChildView mouseUp:].
253    // If we get here with gLastDragView being null, that means that the mouse button has already
254    // been released. In that case we need to abort the drag because the OS won't know where to drop
255    // whatever's being dragged, and we might end up with a stuck drag & drop session.
256    return NS_ERROR_FAILURE;
257  }
258
259  mDataItems = aTransferableArray;
260
261  // Save the transferables away in case a promised file callback is invoked.
262  gDraggedTransferables = aTransferableArray;
263
264  // We need to retain the view and the event during the drag in case either
265  // gets destroyed.
266  mNativeDragView = [gLastDragView retain];
267  mNativeDragEvent = [gLastDragMouseDownEvent retain];
268
269  gUserCancelledDrag = false;
270
271  NSPasteboardItem* pbItem = [NSPasteboardItem new];
272  NSMutableArray* types = [NSMutableArray arrayWithCapacity:5];
273
274  if (gDraggedTransferables) {
275    uint32_t count = 0;
276    gDraggedTransferables->GetLength(&count);
277
278    for (uint32_t j = 0; j < count; j++) {
279      nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(aTransferableArray, j);
280      if (!currentTransferable) {
281        return NS_ERROR_FAILURE;
282      }
283
284      // Transform the transferable to an NSDictionary
285      NSDictionary* pasteboardOutputDict =
286          nsClipboard::PasteboardDictFromTransferable(currentTransferable);
287      if (!pasteboardOutputDict) {
288        return NS_ERROR_FAILURE;
289      }
290
291      // write everything out to the general pasteboard
292      [types addObjectsFromArray:[pasteboardOutputDict allKeys]];
293      // Gecko is initiating this drag so we always want its own views to
294      // consider it. Add our wildcard type to the pasteboard to accomplish
295      // this.
296      [types addObject:[UTIHelper stringFromPboardType:kMozWildcardPboardType]];
297    }
298  }
299  [pbItem setDataProvider:mNativeDragView forTypes:types];
300
301  NSPoint draggingPoint;
302  NSImage* image = ConstructDragImage(mSourceNode, aRegion, &draggingPoint);
303
304  NSRect localDragRect = image.alignmentRect;
305  localDragRect.origin.x = draggingPoint.x;
306  localDragRect.origin.y = draggingPoint.y - localDragRect.size.height;
307
308  NSDraggingItem* dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pbItem];
309  [pbItem release];
310  [dragItem setDraggingFrame:localDragRect contents:image];
311
312  nsBaseDragService::StartDragSession();
313  nsBaseDragService::OpenDragPopup();
314
315  NSDraggingSession* draggingSession = [mNativeDragView
316      beginDraggingSessionWithItems:[NSArray arrayWithObject:[dragItem autorelease]]
317                              event:mNativeDragEvent
318                             source:mNativeDragView];
319  draggingSession.animatesToStartingPositionsOnCancelOrFail = YES;
320
321  return NS_OK;
322
323  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
324}
325
326NS_IMETHODIMP
327nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) {
328  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
329
330  if (!aTransferable) return NS_ERROR_FAILURE;
331
332  // get flavor list that includes all acceptable flavors (including ones obtained through
333  // conversion)
334  nsTArray<nsCString> flavors;
335  nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
336  if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
337
338  // if this drag originated within Mozilla we should just use the cached data from
339  // when the drag started if possible
340  if (mDataItems) {
341    nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(mDataItems, aItemIndex);
342    if (currentTransferable) {
343      for (uint32_t i = 0; i < flavors.Length(); i++) {
344        nsCString& flavorStr = flavors[i];
345
346        nsCOMPtr<nsISupports> dataSupports;
347        rv = currentTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(dataSupports));
348        if (NS_SUCCEEDED(rv)) {
349          aTransferable->SetTransferData(flavorStr.get(), dataSupports);
350          return NS_OK;  // maybe try to fill in more types? Is there a point?
351        }
352      }
353    }
354  }
355
356  // now check the actual clipboard for data
357  for (uint32_t i = 0; i < flavors.Length(); i++) {
358    nsCString& flavorStr = flavors[i];
359
360    MOZ_LOG(sCocoaLog, LogLevel::Info,
361            ("nsDragService::GetData: looking for clipboard data of type %s\n", flavorStr.get()));
362
363    NSArray* droppedItems = [globalDragPboard pasteboardItems];
364    if (!droppedItems) {
365      continue;
366    }
367
368    uint32_t itemCount = [droppedItems count];
369    if (aItemIndex >= itemCount) {
370      continue;
371    }
372
373    NSPasteboardItem* item = [droppedItems objectAtIndex:aItemIndex];
374    if (!item) {
375      continue;
376    }
377
378    if (flavorStr.EqualsLiteral(kFileMime)) {
379      NSString* filePath = GetFilePath(item);
380      if (!filePath) continue;
381
382      unsigned int stringLength = [filePath length];
383      unsigned int dataLength = (stringLength + 1) * sizeof(char16_t);  // in bytes
384      char16_t* clipboardDataPtr = (char16_t*)malloc(dataLength);
385      if (!clipboardDataPtr) return NS_ERROR_OUT_OF_MEMORY;
386      [filePath getCharacters:reinterpret_cast<unichar*>(clipboardDataPtr)];
387      clipboardDataPtr[stringLength] = 0;  // null terminate
388
389      nsCOMPtr<nsIFile> file;
390      rv = NS_NewLocalFile(nsDependentString(clipboardDataPtr), true, getter_AddRefs(file));
391      free(clipboardDataPtr);
392      if (NS_FAILED(rv)) continue;
393
394      aTransferable->SetTransferData(flavorStr.get(), file);
395
396      break;
397    } else if (flavorStr.EqualsLiteral(kCustomTypesMime)) {
398      NSString* availableType =
399          [item availableTypeFromArray:[NSArray arrayWithObject:kMozCustomTypesPboardType]];
400      if (!availableType || !IsValidType(availableType, false)) {
401        continue;
402      }
403      NSData* pasteboardData = [item dataForType:availableType];
404      if (!pasteboardData) {
405        continue;
406      }
407
408      unsigned int dataLength = [pasteboardData length];
409      void* clipboardDataPtr = malloc(dataLength);
410      if (!clipboardDataPtr) {
411        return NS_ERROR_OUT_OF_MEMORY;
412      }
413      [pasteboardData getBytes:clipboardDataPtr];
414
415      nsCOMPtr<nsISupports> genericDataWrapper;
416      nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtr, dataLength,
417                                                 getter_AddRefs(genericDataWrapper));
418
419      aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
420      free(clipboardDataPtr);
421      break;
422    }
423
424    NSString* pString = nil;
425    if (flavorStr.EqualsLiteral(kUnicodeMime)) {
426      pString = GetStringForType(item, [UTIHelper stringFromPboardType:NSPasteboardTypeString]);
427    } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
428      pString = GetStringForType(item, [UTIHelper stringFromPboardType:NSPasteboardTypeHTML]);
429    } else if (flavorStr.EqualsLiteral(kURLMime)) {
430      pString = GetStringForType(item, [UTIHelper stringFromPboardType:kPublicUrlPboardType]);
431      if (pString) {
432        NSString* title = GetTitleForURL(item);
433        if (!title) {
434          title = pString;
435        }
436        pString = [NSString stringWithFormat:@"%@\n%@", pString, title];
437      }
438    } else if (flavorStr.EqualsLiteral(kURLDataMime)) {
439      pString = GetStringForType(item, [UTIHelper stringFromPboardType:kPublicUrlPboardType]);
440    } else if (flavorStr.EqualsLiteral(kURLDescriptionMime)) {
441      pString = GetTitleForURL(item);
442    } else if (flavorStr.EqualsLiteral(kRTFMime)) {
443      pString = GetStringForType(item, [UTIHelper stringFromPboardType:NSPasteboardTypeRTF]);
444    }
445    if (pString) {
446      NSData* stringData;
447      if (flavorStr.EqualsLiteral(kRTFMime)) {
448        stringData = [pString dataUsingEncoding:NSASCIIStringEncoding];
449      } else {
450        stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
451      }
452      unsigned int dataLength = [stringData length];
453      void* clipboardDataPtr = malloc(dataLength);
454      if (!clipboardDataPtr) return NS_ERROR_OUT_OF_MEMORY;
455      [stringData getBytes:clipboardDataPtr];
456
457      // The DOM only wants LF, so convert from MacOS line endings to DOM line endings.
458      int32_t signedDataLength = dataLength;
459      nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &clipboardDataPtr,
460                                                         &signedDataLength);
461      dataLength = signedDataLength;
462
463      // skip BOM (Byte Order Mark to distinguish little or big endian)
464      char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
465      if ((dataLength > 2) &&
466          ((clipboardDataPtrNoBOM[0] == 0xFEFF) || (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
467        dataLength -= sizeof(char16_t);
468        clipboardDataPtrNoBOM += 1;
469      }
470
471      nsCOMPtr<nsISupports> genericDataWrapper;
472      nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, clipboardDataPtrNoBOM, dataLength,
473                                                 getter_AddRefs(genericDataWrapper));
474      aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
475      free(clipboardDataPtr);
476      break;
477    }
478
479    // We have never supported this on Mac OS X, we should someday. Normally dragging images
480    // in is accomplished with a file path drag instead of the image data itself.
481    /*
482    if (flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kJPEGImageMime) ||
483        flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime)) {
484
485    }
486    */
487  }
488  return NS_OK;
489
490  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
491}
492
493NS_IMETHODIMP
494nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
495  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
496
497  *_retval = false;
498
499  if (!globalDragPboard) return NS_ERROR_FAILURE;
500
501  nsDependentCString dataFlavor(aDataFlavor);
502
503  // first see if we have data for this in our cached transferable
504  if (mDataItems) {
505    uint32_t dataItemsCount;
506    mDataItems->GetLength(&dataItemsCount);
507    for (unsigned int i = 0; i < dataItemsCount; i++) {
508      nsCOMPtr<nsITransferable> currentTransferable = do_QueryElementAt(mDataItems, i);
509      if (!currentTransferable) continue;
510
511      nsTArray<nsCString> flavors;
512      nsresult rv = currentTransferable->FlavorsTransferableCanImport(flavors);
513      if (NS_FAILED(rv)) continue;
514
515      for (uint32_t j = 0; j < flavors.Length(); j++) {
516        if (dataFlavor.Equals(flavors[j])) {
517          *_retval = true;
518          return NS_OK;
519        }
520      }
521    }
522  }
523
524  const NSString* type = nil;
525  bool allowFileURL = false;
526  if (dataFlavor.EqualsLiteral(kFileMime)) {
527    type = [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL];
528    allowFileURL = true;
529  } else if (dataFlavor.EqualsLiteral(kUnicodeMime)) {
530    type = [UTIHelper stringFromPboardType:NSPasteboardTypeString];
531  } else if (dataFlavor.EqualsLiteral(kHTMLMime)) {
532    type = [UTIHelper stringFromPboardType:NSPasteboardTypeHTML];
533  } else if (dataFlavor.EqualsLiteral(kURLMime) || dataFlavor.EqualsLiteral(kURLDataMime)) {
534    type = [UTIHelper stringFromPboardType:kPublicUrlPboardType];
535  } else if (dataFlavor.EqualsLiteral(kURLDescriptionMime)) {
536    type = [UTIHelper stringFromPboardType:kPublicUrlNamePboardType];
537  } else if (dataFlavor.EqualsLiteral(kRTFMime)) {
538    type = [UTIHelper stringFromPboardType:NSPasteboardTypeRTF];
539  } else if (dataFlavor.EqualsLiteral(kCustomTypesMime)) {
540    type = [UTIHelper stringFromPboardType:kMozCustomTypesPboardType];
541  }
542
543  NSString* availableType =
544      [globalDragPboard availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]];
545  if (availableType && IsValidType(availableType, allowFileURL)) {
546    *_retval = true;
547  }
548
549  return NS_OK;
550
551  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
552}
553
554NS_IMETHODIMP
555nsDragService::GetNumDropItems(uint32_t* aNumItems) {
556  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
557
558  *aNumItems = 0;
559
560  // first check to see if we have a number of items cached
561  if (mDataItems) {
562    mDataItems->GetLength(aNumItems);
563    return NS_OK;
564  }
565
566  NSArray* droppedItems = [globalDragPboard pasteboardItems];
567  if (droppedItems) {
568    *aNumItems = [droppedItems count];
569  }
570
571  return NS_OK;
572
573  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
574}
575
576NS_IMETHODIMP
577nsDragService::UpdateDragImage(nsINode* aImage, int32_t aImageX, int32_t aImageY) {
578  nsBaseDragService::UpdateDragImage(aImage, aImageX, aImageY);
579  mDragImageChanged = true;
580  return NS_OK;
581}
582
583void nsDragService::DragMovedWithView(NSDraggingSession* aSession, NSPoint aPoint) {
584  aPoint.y = nsCocoaUtils::FlippedScreenY(aPoint.y);
585
586  // XXX It feels like we should be using the backing scale factor at aPoint
587  // rather than the initial drag view, but I've seen no ill effects of this.
588  CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mNativeDragView);
589  LayoutDeviceIntPoint devPoint = nsCocoaUtils::CocoaPointsToDevPixels(aPoint, scaleFactor);
590
591  // If the image has changed, call enumerateDraggingItemsWithOptions to get
592  // the item being dragged and update its image.
593  if (mDragImageChanged && mNativeDragView) {
594    mDragImageChanged = false;
595
596    nsPresContext* pc = nullptr;
597    nsCOMPtr<nsIContent> content = do_QueryInterface(mImage);
598    if (content) {
599      pc = content->OwnerDoc()->GetPresContext();
600    }
601
602    if (pc) {
603      void (^changeImageBlock)(NSDraggingItem*, NSInteger, BOOL*) =
604          ^(NSDraggingItem* draggingItem, NSInteger idx, BOOL* stop) {
605            // We never add more than one item right now, but check just in case.
606            if (idx > 0) {
607              return;
608            }
609
610            nsPoint pt = LayoutDevicePixel::ToAppUnits(
611                devPoint, pc->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
612            CSSIntPoint screenPoint = CSSIntPoint(nsPresContext::AppUnitsToIntCSSPixels(pt.x),
613                                                  nsPresContext::AppUnitsToIntCSSPixels(pt.y));
614
615            // Create a new image; if one isn't returned don't change the current one.
616            LayoutDeviceIntRect newRect;
617            NSImage* image = ConstructDragImage(mSourceNode, Nothing(), screenPoint, &newRect);
618            if (image) {
619              NSRect draggingRect = nsCocoaUtils::GeckoRectToCocoaRectDevPix(newRect, scaleFactor);
620              [draggingItem setDraggingFrame:draggingRect contents:image];
621            }
622          };
623
624      [aSession enumerateDraggingItemsWithOptions:NSDraggingItemEnumerationConcurrent
625                                          forView:nil
626                                          classes:[NSArray arrayWithObject:[NSPasteboardItem class]]
627                                    searchOptions:@{}
628                                       usingBlock:changeImageBlock];
629    }
630  }
631
632  DragMoved(devPoint.x, devPoint.y);
633}
634
635NS_IMETHODIMP
636nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
637  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
638
639  if (mNativeDragView) {
640    [mNativeDragView release];
641    mNativeDragView = nil;
642  }
643  if (mNativeDragEvent) {
644    [mNativeDragEvent release];
645    mNativeDragEvent = nil;
646  }
647
648  mUserCancelled = gUserCancelledDrag;
649
650  nsresult rv = nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
651  mDataItems = nullptr;
652  return rv;
653
654  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
655}
656