Backed out changeset b4a0f8afc02e (bug 1857946) for causing bc failures at browser...
[gecko.git] / widget / cocoa / nsDragService.mm
blob26bb834a89e6ed84ea534b87ad278f4728c28cc3
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 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);
60   NSImage* image =
61       ConstructDragImage(mSourceNode, aRegion, mScreenPosition, &dragRect);
62   if (!image) {
63     // if no image was returned, just draw a rectangle
64     NSSize size;
65     size.width =
66         nsCocoaUtils::DevPixelsToCocoaPoints(dragRect.width, scaleFactor);
67     size.height =
68         nsCocoaUtils::DevPixelsToCocoaPoints(dragRect.height, scaleFactor);
69     image = [NSImage imageWithSize:size
70                            flipped:YES
71                     drawingHandler:^BOOL(NSRect dstRect) {
72                       [[NSColor grayColor] set];
73                       NSBezierPath* path =
74                           [NSBezierPath bezierPathWithRect:dstRect];
75                       [path setLineWidth:2.0];
76                       [path stroke];
77                       return YES;
78                     }];
79   }
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];
88   return image;
90   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
93 NSImage* nsDragService::ConstructDragImage(nsINode* aDOMNode,
94                                            const Maybe<CSSIntRegion>& aRegion,
95                                            CSSIntPoint aPoint,
96                                            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),
108                        pc->CSSPixelsToDevPixels(aPoint.y), size, 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 = Factory::CreateDataSourceSurface(
117       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,
125       dataSurface->GetFormat());
126   if (!dt) {
127     dataSurface->Unmap();
128     return nil;
129   }
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
137                                               pixelsWide:width
138                                               pixelsHigh:height
139                                            bitsPerSample:8
140                                          samplesPerPixel:4
141                                                 hasAlpha:YES
142                                                 isPlanar:NO
143                                           colorSpaceName:NSDeviceRGBColorSpace
144                                              bytesPerRow:width * 4
145                                             bitsPerPixel:32];
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
153       // as well.
154 #ifdef IS_BIG_ENDIAN
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);
159 #else
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);
164 #endif
165       src += 4;
166       dest += 4;
167     }
168   }
169   dataSurface->Unmap();
171   NSImage* image = [[NSImage alloc]
172       initWithSize:NSMakeSize(width / scaleFactor, height / scaleFactor)];
173   [image addRepresentation:imageRep];
174   [imageRep release];
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;
186 #ifdef NIGHTLY_BUILD
187   MOZ_RELEASE_ASSERT(NS_IsMainThread());
188 #endif
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;
197   }
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
205   // gets destroyed.
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) {
215     uint32_t count = 0;
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;
223       }
225       // Transform the transferable to an NSDictionary
226       NSDictionary* pasteboardOutputDict =
227           nsClipboard::PasteboardDictFromTransferable(currentTransferable);
228       if (!pasteboardOutputDict) {
229         return NS_ERROR_FAILURE;
230       }
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
236       // this.
237       [types addObject:[UTIHelper stringFromPboardType:kMozWildcardPboardType]];
238     }
239   }
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];
251   [pbItem release];
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();
265   return NS_OK;
267   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
270 NS_IMETHODIMP
271 nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) {
272   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
274   if (!aTransferable) {
275     return NS_ERROR_FAILURE;
276   }
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);
282   if (NS_FAILED(rv)) {
283     return NS_ERROR_FAILURE;
284   }
286   // if this drag originated within Mozilla we should just use the cached data
287   // from when the drag started if possible
288   if (mDataItems) {
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?
301         }
302       }
303     }
304   }
306   NSArray* droppedItems = [globalDragPboard pasteboardItems];
307   if (!droppedItems) {
308     return NS_ERROR_FAILURE;
309   }
311   uint32_t itemCount = [droppedItems count];
312   if (aItemIndex >= itemCount) {
313     return NS_ERROR_FAILURE;
314   }
316   NSPasteboardItem* item = [droppedItems objectAtIndex:aItemIndex];
317   if (!item) {
318     return NS_ERROR_FAILURE;
319   }
321   // now check the actual clipboard for data
322   for (uint32_t i = 0; i < flavors.Length(); i++) {
323     nsCocoaUtils::SetTransferDataForTypeFromPasteboardItem(aTransferable,
324                                                            flavors[i], item);
325   }
327   return NS_OK;
329   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
332 NS_IMETHODIMP
333 nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
334   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
336   *_retval = false;
338   if (!globalDragPboard) return NS_ERROR_FAILURE;
340   nsDependentCString dataFlavor(aDataFlavor);
342   // first see if we have data for this in our cached transferable
343   if (mDataItems) {
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])) {
357           *_retval = true;
358           return NS_OK;
359         }
360       }
361     }
362   }
364   const NSString* type = nil;
365   bool allowFileURL = false;
366   if (dataFlavor.EqualsLiteral(kFileMime)) {
367     type = [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL];
368     allowFileURL = true;
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];
382   }
384   NSString* availableType = [globalDragPboard
385       availableTypeFromArray:[NSArray arrayWithObjects:(id)type, nil]];
386   if (availableType &&
387       nsCocoaUtils::IsValidPasteboardType(availableType, allowFileURL)) {
388     *_retval = true;
389   }
391   return NS_OK;
393   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
396 NS_IMETHODIMP
397 nsDragService::GetNumDropItems(uint32_t* aNumItems) {
398   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
400   *aNumItems = 0;
402   // first check to see if we have a number of items cached
403   if (mDataItems) {
404     mDataItems->GetLength(aNumItems);
405     return NS_OK;
406   }
408   NSArray* droppedItems = [globalDragPboard pasteboardItems];
409   if (droppedItems) {
410     *aNumItems = [droppedItems count];
411   }
413   return NS_OK;
415   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
418 NS_IMETHODIMP
419 nsDragService::UpdateDragImage(nsINode* aImage, int32_t aImageX,
420                                int32_t aImageY) {
421   nsBaseDragService::UpdateDragImage(aImage, aImageX, aImageY);
422   mDragImageChanged = true;
423   return NS_OK;
426 void nsDragService::DragMovedWithView(NSDraggingSession* aSession,
427                                       NSPoint aPoint) {
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);
443     if (content) {
444       pc = content->OwnerDoc()->GetPresContext();
445     }
447     if (pc) {
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.
451         if (idx > 0) {
452           return;
453         }
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
462         // one.
463         LayoutDeviceIntRect newRect;
464         NSImage* image =
465             ConstructDragImage(mSourceNode, Nothing(), screenPoint, &newRect);
466         if (image) {
467           NSRect draggingRect =
468               nsCocoaUtils::GeckoRectToCocoaRectDevPix(newRect, scaleFactor);
469           [draggingItem setDraggingFrame:draggingRect contents:image];
470         }
471       };
473       [aSession
474           enumerateDraggingItemsWithOptions:NSDraggingItemEnumerationConcurrent
475                                     forView:nil
476                                     classes:[NSArray
477                                                 arrayWithObject:
478                                                     [NSPasteboardItem class]]
479                               searchOptions:@{}
480                                  usingBlock:changeImageBlock];
481     }
482   }
484   DragMoved(devPoint.x, devPoint.y);
487 NS_IMETHODIMP
488 nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
489   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
491   if (mNativeDragView) {
492     [mNativeDragView release];
493     mNativeDragView = nil;
494   }
495   if (mNativeDragEvent) {
496     [mNativeDragEvent release];
497     mNativeDragEvent = nil;
498   }
500   mUserCancelled = gUserCancelledDrag;
502   nsresult rv = nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
503   mDataItems = nullptr;
504   return rv;
506   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);