1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim: set ts=4 et sw=4 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsDragService.h"
8 #include "nsArrayUtils.h"
9 #include "nsIObserverService.h"
10 #include "nsWidgetsCID.h"
11 #include "nsWindow.h"
12 #include "nsIServiceManager.h"
13 #include "nsXPCOM.h"
14 #include "nsISupportsPrimitives.h"
15 #include "nsIIOService.h"
16 #include "nsIFileURL.h"
17 #include "nsNetUtil.h"
18 #include "mozilla/Logging.h"
19 #include "nsTArray.h"
20 #include "nsPrimitiveHelpers.h"
21 #include "prtime.h"
22 #include "prthread.h"
23 #include <dlfcn.h>
24 #include <gtk/gtk.h>
25 #include <gdk/gdkx.h>
26 #include "nsCRT.h"
27 #include "mozilla/BasicEvents.h"
28 #include "mozilla/Services.h"
29 #include "mozilla/ClearOnShutdown.h"
30 
31 #include "gfxXlibSurface.h"
32 #include "gfxContext.h"
33 #include "nsImageToPixbuf.h"
34 #include "nsPresContext.h"
35 #include "nsIContent.h"
36 #include "nsIDocument.h"
37 #include "nsISelection.h"
38 #include "nsViewManager.h"
39 #include "nsIFrame.h"
40 #include "nsGtkUtils.h"
41 #include "nsGtkKeyUtils.h"
42 #include "mozilla/gfx/2D.h"
43 #include "gfxPlatform.h"
44 #include "ScreenHelperGTK.h"
45 #include "nsArrayUtils.h"
46 
47 using namespace mozilla;
48 using namespace mozilla::gfx;
49 
50 // This sets how opaque the drag image is
51 #define DRAG_IMAGE_ALPHA_LEVEL 0.5
52 
53 // These values are copied from GtkDragResult (rather than using GtkDragResult
54 // directly) so that this code can be compiled against versions of GTK+ that
55 // do not have GtkDragResult.
56 // GtkDragResult is available from GTK+ version 2.12.
57 enum { MOZ_GTK_DRAG_RESULT_SUCCESS, MOZ_GTK_DRAG_RESULT_NO_TARGET };
58 
59 static LazyLogModule sDragLm("nsDragService");
60 
61 // data used for synthetic periodic motion events sent to the source widget
62 // grabbing real events for the drag.
63 static guint sMotionEventTimerID;
64 static GdkEvent *sMotionEvent;
65 static GtkWidget *sGrabWidget;
66 
67 static const char gMimeListType[] = "application/x-moz-internal-item-list";
68 static const char gMozUrlType[] = "_NETSCAPE_URL";
69 static const char gTextUriListType[] = "text/uri-list";
70 static const char gTextPlainUTF8Type[] = "text/plain;charset=utf-8";
71 
72 static void invisibleSourceDragBegin(GtkWidget *aWidget,
73                                      GdkDragContext *aContext, gpointer aData);
74 
75 static void invisibleSourceDragEnd(GtkWidget *aWidget, GdkDragContext *aContext,
76                                    gpointer aData);
77 
78 static gboolean invisibleSourceDragFailed(GtkWidget *aWidget,
79                                           GdkDragContext *aContext,
80                                           gint aResult, gpointer aData);
81 
82 static void invisibleSourceDragDataGet(GtkWidget *aWidget,
83                                        GdkDragContext *aContext,
84                                        GtkSelectionData *aSelectionData,
85                                        guint aInfo, guint32 aTime,
86                                        gpointer aData);
87 
nsDragService()88 nsDragService::nsDragService() : mScheduledTask(eDragTaskNone), mTaskSource(0) {
89   // We have to destroy the hidden widget before the event loop stops
90   // running.
91   nsCOMPtr<nsIObserverService> obsServ =
92       mozilla::services::GetObserverService();
93   obsServ->AddObserver(this, "quit-application", false);
94 
95   // our hidden source widget
96   // Using an offscreen window works around bug 983843.
97   mHiddenWidget = gtk_offscreen_window_new();
98   // make sure that the widget is realized so that
99   // we can use it as a drag source.
100   gtk_widget_realize(mHiddenWidget);
101   // hook up our internal signals so that we can get some feedback
102   // from our drag source
103   g_signal_connect(mHiddenWidget, "drag_begin",
104                    G_CALLBACK(invisibleSourceDragBegin), this);
105   g_signal_connect(mHiddenWidget, "drag_data_get",
106                    G_CALLBACK(invisibleSourceDragDataGet), this);
107   g_signal_connect(mHiddenWidget, "drag_end",
108                    G_CALLBACK(invisibleSourceDragEnd), this);
109   // drag-failed is available from GTK+ version 2.12
110   guint dragFailedID =
111       g_signal_lookup("drag-failed", G_TYPE_FROM_INSTANCE(mHiddenWidget));
112   if (dragFailedID) {
113     g_signal_connect_closure_by_id(
114         mHiddenWidget, dragFailedID, 0,
115         g_cclosure_new(G_CALLBACK(invisibleSourceDragFailed), this, nullptr),
116         FALSE);
117   }
118 
119   // set up our logging module
120   MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::nsDragService"));
121   mCanDrop = false;
122   mTargetDragDataReceived = false;
123   mTargetDragData = 0;
124   mTargetDragDataLen = 0;
125 }
126 
~nsDragService()127 nsDragService::~nsDragService() {
128   MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::~nsDragService"));
129   if (mTaskSource) g_source_remove(mTaskSource);
130 }
131 
132 NS_IMPL_ISUPPORTS_INHERITED(nsDragService, nsBaseDragService, nsIObserver)
133 
134 mozilla::StaticRefPtr<nsDragService> sDragServiceInstance;
GetInstance()135 /* static */ already_AddRefed<nsDragService> nsDragService::GetInstance() {
136   if (gfxPlatform::IsHeadless()) {
137     return nullptr;
138   }
139   if (!sDragServiceInstance) {
140     sDragServiceInstance = new nsDragService();
141     ClearOnShutdown(&sDragServiceInstance);
142   }
143 
144   RefPtr<nsDragService> service = sDragServiceInstance.get();
145   return service.forget();
146 }
147 
148 // nsIObserver
149 
150 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)151 nsDragService::Observe(nsISupports *aSubject, const char *aTopic,
152                        const char16_t *aData) {
153   if (!nsCRT::strcmp(aTopic, "quit-application")) {
154     MOZ_LOG(sDragLm, LogLevel::Debug,
155             ("nsDragService::Observe(\"quit-application\")"));
156     if (mHiddenWidget) {
157       gtk_widget_destroy(mHiddenWidget);
158       mHiddenWidget = 0;
159     }
160     TargetResetData();
161   } else {
162     NS_NOTREACHED("unexpected topic");
163     return NS_ERROR_UNEXPECTED;
164   }
165 
166   return NS_OK;
167 }
168 
169 // Support for periodic drag events
170 
171 // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
172 // and the Xdnd protocol both recommend that drag events are sent periodically,
173 // but GTK does not normally provide this.
174 //
175 // Here GTK is periodically stimulated by copies of the most recent mouse
176 // motion events so as to send drag position messages to the destination when
177 // appropriate (after it has received a status event from the previous
178 // message).
179 //
180 // (If events were sent only on the destination side then the destination
181 // would have no message to which it could reply with a drag status.  Without
182 // sending a drag status to the source, the destination would not be able to
183 // change its feedback re whether it could accept the drop, and so the
184 // source's behavior on drop will not be consistent.)
185 
DispatchMotionEventCopy(gpointer aData)186 static gboolean DispatchMotionEventCopy(gpointer aData) {
187   // Clear the timer id before OnSourceGrabEventAfter is called during event
188   // dispatch.
189   sMotionEventTimerID = 0;
190 
191   GdkEvent *event = sMotionEvent;
192   sMotionEvent = nullptr;
193   // If there is no longer a grab on the widget, then the drag is over and
194   // there is no need to continue drag motion.
195   if (gtk_widget_has_grab(sGrabWidget)) {
196     gtk_propagate_event(sGrabWidget, event);
197   }
198   gdk_event_free(event);
199 
200   // Cancel this timer;
201   // We've already started another if the motion event was dispatched.
202   return FALSE;
203 }
204 
OnSourceGrabEventAfter(GtkWidget * widget,GdkEvent * event,gpointer user_data)205 static void OnSourceGrabEventAfter(GtkWidget *widget, GdkEvent *event,
206                                    gpointer user_data) {
207   // If there is no longer a grab on the widget, then the drag motion is
208   // over (though the data may not be fetched yet).
209   if (!gtk_widget_has_grab(sGrabWidget)) return;
210 
211   if (event->type == GDK_MOTION_NOTIFY) {
212     if (sMotionEvent) {
213       gdk_event_free(sMotionEvent);
214     }
215     sMotionEvent = gdk_event_copy(event);
216 
217     // Update the cursor position.  The last of these recorded gets used for
218     // the eDragEnd event.
219     nsDragService *dragService = static_cast<nsDragService *>(user_data);
220     gint scale = ScreenHelperGTK::GetGTKMonitorScaleFactor();
221     auto p = LayoutDeviceIntPoint::Round(event->motion.x_root * scale,
222                                          event->motion.y_root * scale);
223     dragService->SetDragEndPoint(p);
224   } else if (sMotionEvent &&
225              (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE)) {
226     // Update modifier state from key events.
227     sMotionEvent->motion.state = event->key.state;
228   } else {
229     return;
230   }
231 
232   if (sMotionEventTimerID) {
233     g_source_remove(sMotionEventTimerID);
234   }
235 
236   // G_PRIORITY_DEFAULT_IDLE is lower priority than GDK's redraw idle source
237   // and lower than GTK's idle source that sends drag position messages after
238   // motion-notify signals.
239   //
240   // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
241   // recommends an interval of 350ms +/- 200ms.
242   sMotionEventTimerID = g_timeout_add_full(
243       G_PRIORITY_DEFAULT_IDLE, 350, DispatchMotionEventCopy, nullptr, nullptr);
244 }
245 
GetGtkWindow(nsIDOMDocument * aDocument)246 static GtkWindow *GetGtkWindow(nsIDOMDocument *aDocument) {
247   nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDocument);
248   if (!doc) return nullptr;
249 
250   nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
251   if (!presShell) return nullptr;
252 
253   RefPtr<nsViewManager> vm = presShell->GetViewManager();
254   if (!vm) return nullptr;
255 
256   nsCOMPtr<nsIWidget> widget;
257   vm->GetRootWidget(getter_AddRefs(widget));
258   if (!widget) return nullptr;
259 
260   GtkWidget *gtkWidget =
261       static_cast<nsWindow *>(widget.get())->GetMozContainerWidget();
262   if (!gtkWidget) return nullptr;
263 
264   GtkWidget *toplevel = nullptr;
265   toplevel = gtk_widget_get_toplevel(gtkWidget);
266   if (!GTK_IS_WINDOW(toplevel)) return nullptr;
267 
268   return GTK_WINDOW(toplevel);
269 }
270 
271 // nsIDragService
272 
273 NS_IMETHODIMP
InvokeDragSession(nsIDOMNode * aDOMNode,const nsACString & aPrincipalURISpec,nsIArray * aArrayTransferables,nsIScriptableRegion * aRegion,uint32_t aActionType,nsContentPolicyType aContentPolicyType=nsIContentPolicy::TYPE_OTHER)274 nsDragService::InvokeDragSession(
275     nsIDOMNode *aDOMNode, const nsACString &aPrincipalURISpec,
276     nsIArray *aArrayTransferables, nsIScriptableRegion *aRegion,
277     uint32_t aActionType,
278     nsContentPolicyType aContentPolicyType = nsIContentPolicy::TYPE_OTHER) {
279   MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::InvokeDragSession"));
280 
281   // If the previous source drag has not yet completed, signal handlers need
282   // to be removed from sGrabWidget and dragend needs to be dispatched to
283   // the source node, but we can't call EndDragSession yet because we don't
284   // know whether or not the drag succeeded.
285   if (mSourceNode) return NS_ERROR_NOT_AVAILABLE;
286 
287   return nsBaseDragService::InvokeDragSession(aDOMNode, aPrincipalURISpec,
288                                               aArrayTransferables, aRegion,
289                                               aActionType, aContentPolicyType);
290 }
291 
292 // nsBaseDragService
InvokeDragSessionImpl(nsIArray * aArrayTransferables,nsIScriptableRegion * aRegion,uint32_t aActionType)293 nsresult nsDragService::InvokeDragSessionImpl(nsIArray *aArrayTransferables,
294                                               nsIScriptableRegion *aRegion,
295                                               uint32_t aActionType) {
296   // make sure that we have an array of transferables to use
297   if (!aArrayTransferables) return NS_ERROR_INVALID_ARG;
298   // set our reference to the transferables.  this will also addref
299   // the transferables since we're going to hang onto this beyond the
300   // length of this call
301   mSourceDataItems = aArrayTransferables;
302   // get the list of items we offer for drags
303   GtkTargetList *sourceList = GetSourceList();
304 
305   if (!sourceList) return NS_OK;
306 
307   // stored temporarily until the drag-begin signal has been received
308   mSourceRegion = aRegion;
309 
310   // save our action type
311   GdkDragAction action = GDK_ACTION_DEFAULT;
312 
313   if (aActionType & DRAGDROP_ACTION_COPY)
314     action = (GdkDragAction)(action | GDK_ACTION_COPY);
315   if (aActionType & DRAGDROP_ACTION_MOVE)
316     action = (GdkDragAction)(action | GDK_ACTION_MOVE);
317   if (aActionType & DRAGDROP_ACTION_LINK)
318     action = (GdkDragAction)(action | GDK_ACTION_LINK);
319 
320   // Create a fake event for the drag so we can pass the time (so to speak).
321   // If we don't do this, then, when the timestamp for the pending button
322   // release event is used for the ungrab, the ungrab can fail due to the
323   // timestamp being _earlier_ than CurrentTime.
324   GdkEvent event;
325   memset(&event, 0, sizeof(GdkEvent));
326   event.type = GDK_BUTTON_PRESS;
327   event.button.window = gtk_widget_get_window(mHiddenWidget);
328   event.button.time = nsWindow::GetLastUserInputTime();
329 
330   // Put the drag widget in the window group of the source node so that the
331   // gtk_grab_add during gtk_drag_begin is effective.
332   // gtk_window_get_group(nullptr) returns the default window group.
333   GtkWindowGroup *window_group =
334       gtk_window_get_group(GetGtkWindow(mSourceDocument));
335   gtk_window_group_add_window(window_group, GTK_WINDOW(mHiddenWidget));
336 
337 #ifdef MOZ_WIDGET_GTK
338   // Get device for event source
339   GdkDisplay *display = gdk_display_get_default();
340   GdkDeviceManager *device_manager = gdk_display_get_device_manager(display);
341   event.button.device = gdk_device_manager_get_client_pointer(device_manager);
342 #endif
343 
344   // start our drag.
345   GdkDragContext *context =
346       gtk_drag_begin(mHiddenWidget, sourceList, action, 1, &event);
347 
348   mSourceRegion = nullptr;
349 
350   nsresult rv;
351   if (context) {
352     StartDragSession();
353 
354     // GTK uses another hidden window for receiving mouse events.
355     sGrabWidget = gtk_window_group_get_current_grab(window_group);
356     if (sGrabWidget) {
357       g_object_ref(sGrabWidget);
358       // Only motion and key events are required but connect to
359       // "event-after" as this is never blocked by other handlers.
360       g_signal_connect(sGrabWidget, "event-after",
361                        G_CALLBACK(OnSourceGrabEventAfter), this);
362     }
363     // We don't have a drag end point yet.
364     mEndDragPoint = LayoutDeviceIntPoint(-1, -1);
365     rv = NS_OK;
366   } else {
367     rv = NS_ERROR_FAILURE;
368   }
369 
370   gtk_target_list_unref(sourceList);
371 
372   return rv;
373 }
374 
SetAlphaPixmap(SourceSurface * aSurface,GdkDragContext * aContext,int32_t aXOffset,int32_t aYOffset,const LayoutDeviceIntRect & dragRect)375 bool nsDragService::SetAlphaPixmap(SourceSurface *aSurface,
376                                    GdkDragContext *aContext, int32_t aXOffset,
377                                    int32_t aYOffset,
378                                    const LayoutDeviceIntRect &dragRect) {
379   GdkScreen *screen = gtk_widget_get_screen(mHiddenWidget);
380 
381   // Transparent drag icons need, like a lot of transparency-related things,
382   // a compositing X window manager
383   if (!gdk_screen_is_composited(screen)) return false;
384 
385 #ifdef cairo_image_surface_create
386 #error "Looks like we're including Mozilla's cairo instead of system cairo"
387 #endif
388   // Prior to GTK 3.9.12, cairo surfaces passed into gtk_drag_set_icon_surface
389   // had their shape information derived from the alpha channel and used with
390   // the X SHAPE extension instead of being displayed as an ARGB window.
391   // See bug 1249604.
392   if (gtk_check_version(3, 9, 12)) return false;
393 
394   // TODO: grab X11 pixmap or image data instead of expensive readback.
395   cairo_surface_t *surf = cairo_image_surface_create(
396       CAIRO_FORMAT_ARGB32, dragRect.width, dragRect.height);
397   if (!surf) return false;
398 
399   RefPtr<DrawTarget> dt = gfxPlatform::CreateDrawTargetForData(
400       cairo_image_surface_get_data(surf),
401       nsIntSize(dragRect.width, dragRect.height),
402       cairo_image_surface_get_stride(surf), SurfaceFormat::B8G8R8A8);
403   if (!dt) return false;
404 
405   dt->ClearRect(Rect(0, 0, dragRect.width, dragRect.height));
406   dt->DrawSurface(
407       aSurface, Rect(0, 0, dragRect.width, dragRect.height),
408       Rect(0, 0, dragRect.width, dragRect.height), DrawSurfaceOptions(),
409       DrawOptions(DRAG_IMAGE_ALPHA_LEVEL, CompositionOp::OP_SOURCE));
410 
411   cairo_surface_mark_dirty(surf);
412   cairo_surface_set_device_offset(surf, -aXOffset, -aYOffset);
413 
414   // Ensure that the surface is drawn at the correct scale on HiDPI displays.
415   static auto sCairoSurfaceSetDeviceScalePtr =
416       (void (*)(cairo_surface_t *, double, double))dlsym(
417           RTLD_DEFAULT, "cairo_surface_set_device_scale");
418   if (sCairoSurfaceSetDeviceScalePtr) {
419     gint scale = ScreenHelperGTK::GetGTKMonitorScaleFactor();
420     sCairoSurfaceSetDeviceScalePtr(surf, scale, scale);
421   }
422 
423   gtk_drag_set_icon_surface(aContext, surf);
424   cairo_surface_destroy(surf);
425   return true;
426 }
427 
428 NS_IMETHODIMP
StartDragSession()429 nsDragService::StartDragSession() {
430   MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::StartDragSession"));
431   return nsBaseDragService::StartDragSession();
432 }
433 
434 NS_IMETHODIMP
EndDragSession(bool aDoneDrag,uint32_t aKeyModifiers)435 nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
436   MOZ_LOG(sDragLm, LogLevel::Debug,
437           ("nsDragService::EndDragSession %d", aDoneDrag));
438 
439   if (sGrabWidget) {
440     g_signal_handlers_disconnect_by_func(
441         sGrabWidget, FuncToGpointer(OnSourceGrabEventAfter), this);
442     g_object_unref(sGrabWidget);
443     sGrabWidget = nullptr;
444 
445     if (sMotionEventTimerID) {
446       g_source_remove(sMotionEventTimerID);
447       sMotionEventTimerID = 0;
448     }
449     if (sMotionEvent) {
450       gdk_event_free(sMotionEvent);
451       sMotionEvent = nullptr;
452     }
453   }
454 
455   // unset our drag action
456   SetDragAction(DRAGDROP_ACTION_NONE);
457 
458   // We're done with the drag context.
459   mTargetDragContextForRemote = nullptr;
460 
461   return nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
462 }
463 
464 // nsIDragSession
465 NS_IMETHODIMP
SetCanDrop(bool aCanDrop)466 nsDragService::SetCanDrop(bool aCanDrop) {
467   MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::SetCanDrop %d", aCanDrop));
468   mCanDrop = aCanDrop;
469   return NS_OK;
470 }
471 
472 NS_IMETHODIMP
GetCanDrop(bool * aCanDrop)473 nsDragService::GetCanDrop(bool *aCanDrop) {
474   MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetCanDrop"));
475   *aCanDrop = mCanDrop;
476   return NS_OK;
477 }
478 
UTF16ToNewUTF8(const char16_t * aUTF16,uint32_t aUTF16Len,char ** aUTF8,uint32_t * aUTF8Len)479 static void UTF16ToNewUTF8(const char16_t *aUTF16, uint32_t aUTF16Len,
480                            char **aUTF8, uint32_t *aUTF8Len) {
481   nsDependentSubstring utf16(aUTF16, aUTF16Len);
482   *aUTF8 = ToNewUTF8String(utf16, aUTF8Len);
483 }
484 
UTF8ToNewUTF16(const char * aUTF8,uint32_t aUTF8Len,char16_t ** aUTF16,uint32_t * aUTF16Len)485 static void UTF8ToNewUTF16(const char *aUTF8, uint32_t aUTF8Len,
486                            char16_t **aUTF16, uint32_t *aUTF16Len) {
487   nsDependentCSubstring utf8(aUTF8, aUTF8Len);
488   *aUTF16 = UTF8ToNewUnicode(utf8, aUTF16Len);
489 }
490 
491 // count the number of URIs in some text/uri-list format data.
CountTextUriListItems(const char * data,uint32_t datalen)492 static uint32_t CountTextUriListItems(const char *data, uint32_t datalen) {
493   const char *p = data;
494   const char *endPtr = p + datalen;
495   uint32_t count = 0;
496 
497   while (p < endPtr) {
498     // skip whitespace (if any)
499     while (p < endPtr && *p != '\0' && isspace(*p)) p++;
500     // if we aren't at the end of the line ...
501     if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r') count++;
502     // skip to the end of the line
503     while (p < endPtr && *p != '\0' && *p != '\n') p++;
504     p++;  // skip the actual newline as well.
505   }
506   return count;
507 }
508 
509 // extract an item from text/uri-list formatted data and convert it to
510 // unicode.
GetTextUriListItem(const char * data,uint32_t datalen,uint32_t aItemIndex,char16_t ** convertedText,uint32_t * convertedTextLen)511 static void GetTextUriListItem(const char *data, uint32_t datalen,
512                                uint32_t aItemIndex, char16_t **convertedText,
513                                uint32_t *convertedTextLen) {
514   const char *p = data;
515   const char *endPtr = p + datalen;
516   unsigned int count = 0;
517 
518   *convertedText = nullptr;
519   while (p < endPtr) {
520     // skip whitespace (if any)
521     while (p < endPtr && *p != '\0' && isspace(*p)) p++;
522     // if we aren't at the end of the line, we have a url
523     if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r') count++;
524     // this is the item we are after ...
525     if (aItemIndex + 1 == count) {
526       const char *q = p;
527       while (q < endPtr && *q != '\0' && *q != '\n' && *q != '\r') q++;
528       UTF8ToNewUTF16(p, q - p, convertedText, convertedTextLen);
529       break;
530     }
531     // skip to the end of the line
532     while (p < endPtr && *p != '\0' && *p != '\n') p++;
533     p++;  // skip the actual newline as well.
534   }
535 
536   // didn't find the desired item, so just pass the whole lot
537   if (!*convertedText) {
538     UTF8ToNewUTF16(data, datalen, convertedText, convertedTextLen);
539   }
540 }
541 
542 NS_IMETHODIMP
GetNumDropItems(uint32_t * aNumItems)543 nsDragService::GetNumDropItems(uint32_t *aNumItems) {
544   MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetNumDropItems"));
545 
546   if (!mTargetWidget) {
547     MOZ_LOG(sDragLm, LogLevel::Debug, ("*** warning: GetNumDropItems \
548                called without a valid target widget!\n"));
549     *aNumItems = 0;
550     return NS_OK;
551   }
552 
553   bool isList = IsTargetContextList();
554   if (isList)
555     mSourceDataItems->GetLength(aNumItems);
556   else {
557     GdkAtom gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
558     GetTargetDragData(gdkFlavor);
559     if (mTargetDragData) {
560       const char *data = reinterpret_cast<char *>(mTargetDragData);
561       *aNumItems = CountTextUriListItems(data, mTargetDragDataLen);
562     } else
563       *aNumItems = 1;
564   }
565   MOZ_LOG(sDragLm, LogLevel::Debug, ("%d items", *aNumItems));
566   return NS_OK;
567 }
568 
569 NS_IMETHODIMP
GetData(nsITransferable * aTransferable,uint32_t aItemIndex)570 nsDragService::GetData(nsITransferable *aTransferable, uint32_t aItemIndex) {
571   MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetData %d", aItemIndex));
572 
573   // make sure that we have a transferable
574   if (!aTransferable) return NS_ERROR_INVALID_ARG;
575 
576   if (!mTargetWidget) {
577     MOZ_LOG(sDragLm, LogLevel::Debug, ("*** warning: GetData \
578                called without a valid target widget!\n"));
579     return NS_ERROR_FAILURE;
580   }
581 
582   // get flavor list that includes all acceptable flavors (including
583   // ones obtained through conversion). Flavors are nsISupportsStrings
584   // so that they can be seen from JS.
585   nsCOMPtr<nsIArray> flavorList;
586   nsresult rv =
587       aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
588   if (NS_FAILED(rv)) return rv;
589 
590   // count the number of flavors
591   uint32_t cnt;
592   flavorList->GetLength(&cnt);
593   unsigned int i;
594 
595   // check to see if this is an internal list
596   bool isList = IsTargetContextList();
597 
598   if (isList) {
599     MOZ_LOG(sDragLm, LogLevel::Debug, ("it's a list..."));
600     // find a matching flavor
601     for (i = 0; i < cnt; ++i) {
602       nsCOMPtr<nsISupportsCString> currentFlavor;
603       currentFlavor = do_QueryElementAt(flavorList, i);
604       if (!currentFlavor) continue;
605 
606       nsCString flavorStr;
607       currentFlavor->ToString(getter_Copies(flavorStr));
608       MOZ_LOG(sDragLm, LogLevel::Debug, ("flavor is %s\n", flavorStr.get()));
609       // get the item with the right index
610       nsCOMPtr<nsITransferable> item =
611           do_QueryElementAt(mSourceDataItems, aItemIndex);
612       if (!item) continue;
613 
614       nsCOMPtr<nsISupports> data;
615       uint32_t tmpDataLen = 0;
616       MOZ_LOG(sDragLm, LogLevel::Debug,
617               ("trying to get transfer data for %s\n", flavorStr.get()));
618       rv = item->GetTransferData(flavorStr.get(), getter_AddRefs(data),
619                                  &tmpDataLen);
620       if (NS_FAILED(rv)) {
621         MOZ_LOG(sDragLm, LogLevel::Debug, ("failed.\n"));
622         continue;
623       }
624       MOZ_LOG(sDragLm, LogLevel::Debug, ("succeeded.\n"));
625       rv = aTransferable->SetTransferData(flavorStr.get(), data, tmpDataLen);
626       if (NS_FAILED(rv)) {
627         MOZ_LOG(sDragLm, LogLevel::Debug,
628                 ("fail to set transfer data into transferable!\n"));
629         continue;
630       }
631       // ok, we got the data
632       return NS_OK;
633     }
634     // if we got this far, we failed
635     return NS_ERROR_FAILURE;
636   }
637 
638   // Now walk down the list of flavors. When we find one that is
639   // actually present, copy out the data into the transferable in that
640   // format. SetTransferData() implicitly handles conversions.
641   for (i = 0; i < cnt; ++i) {
642     nsCOMPtr<nsISupportsCString> currentFlavor;
643     currentFlavor = do_QueryElementAt(flavorList, i);
644     if (currentFlavor) {
645       // find our gtk flavor
646       nsCString flavorStr;
647       currentFlavor->ToString(getter_Copies(flavorStr));
648       GdkAtom gdkFlavor = gdk_atom_intern(flavorStr.get(), FALSE);
649       MOZ_LOG(sDragLm, LogLevel::Debug,
650               ("looking for data in type %s, gdk flavor %p\n", flavorStr.get(),
651                gdkFlavor));
652       bool dataFound = false;
653       if (gdkFlavor) {
654         GetTargetDragData(gdkFlavor);
655       }
656       if (mTargetDragData) {
657         MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound = true\n"));
658         dataFound = true;
659       } else {
660         MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound = false\n"));
661 
662         // Dragging and dropping from the file manager would cause us
663         // to parse the source text as a nsIFile URL.
664         if (flavorStr.EqualsLiteral(kFileMime)) {
665           gdkFlavor = gdk_atom_intern(kTextMime, FALSE);
666           GetTargetDragData(gdkFlavor);
667           if (!mTargetDragData) {
668             gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
669             GetTargetDragData(gdkFlavor);
670           }
671           if (mTargetDragData) {
672             const char *text = static_cast<char *>(mTargetDragData);
673             char16_t *convertedText = nullptr;
674             uint32_t convertedTextLen = 0;
675 
676             GetTextUriListItem(text, mTargetDragDataLen, aItemIndex,
677                                &convertedText, &convertedTextLen);
678 
679             if (convertedText) {
680               nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
681               nsCOMPtr<nsIURI> fileURI;
682               rv = ioService->NewURI(NS_ConvertUTF16toUTF8(convertedText),
683                                      nullptr, nullptr, getter_AddRefs(fileURI));
684               if (NS_SUCCEEDED(rv)) {
685                 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv);
686                 if (NS_SUCCEEDED(rv)) {
687                   nsCOMPtr<nsIFile> file;
688                   rv = fileURL->GetFile(getter_AddRefs(file));
689                   if (NS_SUCCEEDED(rv)) {
690                     // The common wrapping code at the end of
691                     // this function assumes the data is text
692                     // and calls text-specific operations.
693                     // Make a secret hideout here for nsIFile
694                     // objects and return early.
695                     aTransferable->SetTransferData(flavorStr.get(), file,
696                                                    convertedTextLen);
697                     g_free(convertedText);
698                     return NS_OK;
699                   }
700                 }
701               }
702               g_free(convertedText);
703             }
704             continue;
705           }
706         }
707 
708         // if we are looking for text/unicode and we fail to find it
709         // on the clipboard first, try again with text/plain. If that
710         // is present, convert it to unicode.
711         if (flavorStr.EqualsLiteral(kUnicodeMime)) {
712           MOZ_LOG(sDragLm, LogLevel::Debug,
713                   ("we were looking for text/unicode... \
714                            trying with text/plain;charset=utf-8\n"));
715           gdkFlavor = gdk_atom_intern(gTextPlainUTF8Type, FALSE);
716           GetTargetDragData(gdkFlavor);
717           if (mTargetDragData) {
718             MOZ_LOG(sDragLm, LogLevel::Debug, ("Got textplain data\n"));
719             const char *castedText = reinterpret_cast<char *>(mTargetDragData);
720             char16_t *convertedText = nullptr;
721             NS_ConvertUTF8toUTF16 ucs2string(castedText, mTargetDragDataLen);
722             convertedText = ToNewUnicode(ucs2string);
723             if (convertedText) {
724               MOZ_LOG(sDragLm, LogLevel::Debug,
725                       ("successfully converted plain text \
726                                    to unicode.\n"));
727               // out with the old, in with the new
728               g_free(mTargetDragData);
729               mTargetDragData = convertedText;
730               mTargetDragDataLen = ucs2string.Length() * 2;
731               dataFound = true;
732             }  // if plain text data on clipboard
733           } else {
734             MOZ_LOG(sDragLm, LogLevel::Debug,
735                     ("we were looking for text/unicode... \
736                                trying again with text/plain\n"));
737             gdkFlavor = gdk_atom_intern(kTextMime, FALSE);
738             GetTargetDragData(gdkFlavor);
739             if (mTargetDragData) {
740               MOZ_LOG(sDragLm, LogLevel::Debug, ("Got textplain data\n"));
741               const char *castedText =
742                   reinterpret_cast<char *>(mTargetDragData);
743               char16_t *convertedText = nullptr;
744               uint32_t convertedTextLen = 0;
745               UTF8ToNewUTF16(castedText, mTargetDragDataLen, &convertedText,
746                              &convertedTextLen);
747               if (convertedText) {
748                 MOZ_LOG(sDragLm, LogLevel::Debug,
749                         ("successfully converted plain text \
750                                        to unicode.\n"));
751                 // out with the old, in with the new
752                 g_free(mTargetDragData);
753                 mTargetDragData = convertedText;
754                 mTargetDragDataLen = convertedTextLen * 2;
755                 dataFound = true;
756               }  // if plain text data on clipboard
757             }    // if plain text flavor present
758           }      // if plain text charset=utf-8 flavor present
759         }        // if looking for text/unicode
760 
761         // if we are looking for text/x-moz-url and we failed to find
762         // it on the clipboard, try again with text/uri-list, and then
763         // _NETSCAPE_URL
764         if (flavorStr.EqualsLiteral(kURLMime)) {
765           MOZ_LOG(sDragLm, LogLevel::Debug,
766                   ("we were looking for text/x-moz-url...\
767                            trying again with text/uri-list\n"));
768           gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
769           GetTargetDragData(gdkFlavor);
770           if (mTargetDragData) {
771             MOZ_LOG(sDragLm, LogLevel::Debug, ("Got text/uri-list data\n"));
772             const char *data = reinterpret_cast<char *>(mTargetDragData);
773             char16_t *convertedText = nullptr;
774             uint32_t convertedTextLen = 0;
775 
776             GetTextUriListItem(data, mTargetDragDataLen, aItemIndex,
777                                &convertedText, &convertedTextLen);
778 
779             if (convertedText) {
780               MOZ_LOG(sDragLm, LogLevel::Debug, ("successfully converted \
781                                    _NETSCAPE_URL to unicode.\n"));
782               // out with the old, in with the new
783               g_free(mTargetDragData);
784               mTargetDragData = convertedText;
785               mTargetDragDataLen = convertedTextLen * 2;
786               dataFound = true;
787             }
788           } else {
789             MOZ_LOG(sDragLm, LogLevel::Debug,
790                     ("failed to get text/uri-list data\n"));
791           }
792           if (!dataFound) {
793             MOZ_LOG(sDragLm, LogLevel::Debug,
794                     ("we were looking for text/x-moz-url...\
795                                trying again with _NETSCAP_URL\n"));
796             gdkFlavor = gdk_atom_intern(gMozUrlType, FALSE);
797             GetTargetDragData(gdkFlavor);
798             if (mTargetDragData) {
799               MOZ_LOG(sDragLm, LogLevel::Debug, ("Got _NETSCAPE_URL data\n"));
800               const char *castedText =
801                   reinterpret_cast<char *>(mTargetDragData);
802               char16_t *convertedText = nullptr;
803               uint32_t convertedTextLen = 0;
804               UTF8ToNewUTF16(castedText, mTargetDragDataLen, &convertedText,
805                              &convertedTextLen);
806               if (convertedText) {
807                 MOZ_LOG(sDragLm, LogLevel::Debug,
808                         ("successfully converted _NETSCAPE_URL \
809                                        to unicode.\n"));
810                 // out with the old, in with the new
811                 g_free(mTargetDragData);
812                 mTargetDragData = convertedText;
813                 mTargetDragDataLen = convertedTextLen * 2;
814                 dataFound = true;
815               }
816             } else {
817               MOZ_LOG(sDragLm, LogLevel::Debug,
818                       ("failed to get _NETSCAPE_URL data\n"));
819             }
820           }
821         }
822 
823       }  // else we try one last ditch effort to find our data
824 
825       if (dataFound) {
826         if (!flavorStr.EqualsLiteral(kCustomTypesMime)) {
827           // the DOM only wants LF, so convert from MacOS line endings
828           // to DOM line endings.
829           nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(
830               flavorStr, &mTargetDragData,
831               reinterpret_cast<int *>(&mTargetDragDataLen));
832         }
833 
834         // put it into the transferable.
835         nsCOMPtr<nsISupports> genericDataWrapper;
836         nsPrimitiveHelpers::CreatePrimitiveForData(
837             flavorStr, mTargetDragData, mTargetDragDataLen,
838             getter_AddRefs(genericDataWrapper));
839         aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper,
840                                        mTargetDragDataLen);
841         // we found one, get out of this loop!
842         MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound and converted!\n"));
843         break;
844       }
845     }  // if (currentFlavor)
846   }    // foreach flavor
847 
848   return NS_OK;
849 }
850 
851 NS_IMETHODIMP
IsDataFlavorSupported(const char * aDataFlavor,bool * _retval)852 nsDragService::IsDataFlavorSupported(const char *aDataFlavor, bool *_retval) {
853   MOZ_LOG(sDragLm, LogLevel::Debug,
854           ("nsDragService::IsDataFlavorSupported %s", aDataFlavor));
855   if (!_retval) return NS_ERROR_INVALID_ARG;
856 
857   // set this to no by default
858   *_retval = false;
859 
860   // check to make sure that we have a drag object set, here
861   if (!mTargetWidget) {
862     MOZ_LOG(sDragLm, LogLevel::Debug, ("*** warning: IsDataFlavorSupported \
863                called without a valid target widget!\n"));
864     return NS_OK;
865   }
866 
867   // check to see if the target context is a list.
868   bool isList = IsTargetContextList();
869   // if it is, just look in the internal data since we are the source
870   // for it.
871   if (isList) {
872     MOZ_LOG(sDragLm, LogLevel::Debug, ("It's a list.."));
873     uint32_t numDragItems = 0;
874     // if we don't have mDataItems we didn't start this drag so it's
875     // an external client trying to fool us.
876     if (!mSourceDataItems) return NS_OK;
877     mSourceDataItems->GetLength(&numDragItems);
878     for (uint32_t itemIndex = 0; itemIndex < numDragItems; ++itemIndex) {
879       nsCOMPtr<nsITransferable> currItem =
880           do_QueryElementAt(mSourceDataItems, itemIndex);
881       if (currItem) {
882         nsCOMPtr<nsIArray> flavorList;
883         currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
884         if (flavorList) {
885           uint32_t numFlavors;
886           flavorList->GetLength(&numFlavors);
887           for (uint32_t flavorIndex = 0; flavorIndex < numFlavors;
888                ++flavorIndex) {
889             nsCOMPtr<nsISupportsCString> currentFlavor;
890             currentFlavor = do_QueryElementAt(flavorList, flavorIndex);
891             if (currentFlavor) {
892               nsCString flavorStr;
893               currentFlavor->ToString(getter_Copies(flavorStr));
894               MOZ_LOG(
895                   sDragLm, LogLevel::Debug,
896                   ("checking %s against %s\n", flavorStr.get(), aDataFlavor));
897               if (flavorStr.Equals(aDataFlavor)) {
898                 MOZ_LOG(sDragLm, LogLevel::Debug, ("boioioioiooioioioing!\n"));
899                 *_retval = true;
900               }
901             }
902           }
903         }
904       }
905     }
906     return NS_OK;
907   }
908 
909   // check the target context vs. this flavor, one at a time
910   GList *tmp;
911   for (tmp = gdk_drag_context_list_targets(mTargetDragContext); tmp;
912        tmp = tmp->next) {
913     /* Bug 331198 */
914     GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
915     gchar *name = nullptr;
916     name = gdk_atom_name(atom);
917     MOZ_LOG(sDragLm, LogLevel::Debug,
918             ("checking %s against %s\n", name, aDataFlavor));
919     if (name && (strcmp(name, aDataFlavor) == 0)) {
920       MOZ_LOG(sDragLm, LogLevel::Debug, ("good!\n"));
921       *_retval = true;
922     }
923     // check for automatic text/uri-list -> text/x-moz-url mapping
924     if (!*_retval && name && (strcmp(name, gTextUriListType) == 0) &&
925         (strcmp(aDataFlavor, kURLMime) == 0 ||
926          strcmp(aDataFlavor, kFileMime) == 0)) {
927       MOZ_LOG(sDragLm, LogLevel::Debug, ("good! ( it's text/uri-list and \
928                    we're checking against text/x-moz-url )\n"));
929       *_retval = true;
930     }
931     // check for automatic _NETSCAPE_URL -> text/x-moz-url mapping
932     if (!*_retval && name && (strcmp(name, gMozUrlType) == 0) &&
933         (strcmp(aDataFlavor, kURLMime) == 0)) {
934       MOZ_LOG(sDragLm, LogLevel::Debug, ("good! ( it's _NETSCAPE_URL and \
935                    we're checking against text/x-moz-url )\n"));
936       *_retval = true;
937     }
938     // check for auto text/plain -> text/unicode mapping
939     if (!*_retval && name && (strcmp(name, kTextMime) == 0) &&
940         ((strcmp(aDataFlavor, kUnicodeMime) == 0) ||
941          (strcmp(aDataFlavor, kFileMime) == 0))) {
942       MOZ_LOG(sDragLm, LogLevel::Debug,
943               ("good! ( it's text plain and we're checking \
944                    against text/unicode or application/x-moz-file)\n"));
945       *_retval = true;
946     }
947     g_free(name);
948   }
949   return NS_OK;
950 }
951 
ReplyToDragMotion(GdkDragContext * aDragContext)952 void nsDragService::ReplyToDragMotion(GdkDragContext *aDragContext) {
953   MOZ_LOG(sDragLm, LogLevel::Debug,
954           ("nsDragService::ReplyToDragMotion %d", mCanDrop));
955 
956   GdkDragAction action = (GdkDragAction)0;
957   if (mCanDrop) {
958     // notify the dragger if we can drop
959     switch (mDragAction) {
960       case DRAGDROP_ACTION_COPY:
961         action = GDK_ACTION_COPY;
962         break;
963       case DRAGDROP_ACTION_LINK:
964         action = GDK_ACTION_LINK;
965         break;
966       case DRAGDROP_ACTION_NONE:
967         action = (GdkDragAction)0;
968         break;
969       default:
970         action = GDK_ACTION_MOVE;
971         break;
972     }
973   }
974 
975   gdk_drag_status(aDragContext, action, mTargetTime);
976 }
977 
TargetDataReceived(GtkWidget * aWidget,GdkDragContext * aContext,gint aX,gint aY,GtkSelectionData * aSelectionData,guint aInfo,guint32 aTime)978 void nsDragService::TargetDataReceived(GtkWidget *aWidget,
979                                        GdkDragContext *aContext, gint aX,
980                                        gint aY,
981                                        GtkSelectionData *aSelectionData,
982                                        guint aInfo, guint32 aTime) {
983   MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::TargetDataReceived"));
984   TargetResetData();
985   mTargetDragDataReceived = true;
986   gint len = gtk_selection_data_get_length(aSelectionData);
987   const guchar *data = gtk_selection_data_get_data(aSelectionData);
988   if (len > 0 && data) {
989     mTargetDragDataLen = len;
990     mTargetDragData = g_malloc(mTargetDragDataLen);
991     memcpy(mTargetDragData, data, mTargetDragDataLen);
992   } else {
993     MOZ_LOG(sDragLm, LogLevel::Debug,
994             ("Failed to get data.  selection data len was %d\n",
995              mTargetDragDataLen));
996   }
997 }
998 
IsTargetContextList(void)999 bool nsDragService::IsTargetContextList(void) {
1000   bool retval = false;
1001 
1002   // gMimeListType drags only work for drags within a single process. The
1003   // gtk_drag_get_source_widget() function will return nullptr if the source
1004   // of the drag is another app, so we use it to check if a gMimeListType
1005   // drop will work or not.
1006   if (gtk_drag_get_source_widget(mTargetDragContext) == nullptr) return retval;
1007 
1008   GList *tmp;
1009 
1010   // walk the list of context targets and see if one of them is a list
1011   // of items.
1012   for (tmp = gdk_drag_context_list_targets(mTargetDragContext); tmp;
1013        tmp = tmp->next) {
1014     /* Bug 331198 */
1015     GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
1016     gchar *name = nullptr;
1017     name = gdk_atom_name(atom);
1018     if (name && strcmp(name, gMimeListType) == 0) retval = true;
1019     g_free(name);
1020     if (retval) break;
1021   }
1022   return retval;
1023 }
1024 
1025 // Maximum time to wait for a "drag_received" arrived, in microseconds
1026 #define NS_DND_TIMEOUT 500000
1027 
GetTargetDragData(GdkAtom aFlavor)1028 void nsDragService::GetTargetDragData(GdkAtom aFlavor) {
1029   MOZ_LOG(sDragLm, LogLevel::Debug, ("getting data flavor %p\n", aFlavor));
1030   MOZ_LOG(sDragLm, LogLevel::Debug,
1031           ("mLastWidget is %p and mLastContext is %p\n", mTargetWidget.get(),
1032            mTargetDragContext.get()));
1033   // reset our target data areas
1034   TargetResetData();
1035   gtk_drag_get_data(mTargetWidget, mTargetDragContext, aFlavor, mTargetTime);
1036 
1037   MOZ_LOG(sDragLm, LogLevel::Debug, ("about to start inner iteration."));
1038   PRTime entryTime = PR_Now();
1039   while (!mTargetDragDataReceived && mDoingDrag) {
1040     // check the number of iterations
1041     MOZ_LOG(sDragLm, LogLevel::Debug, ("doing iteration...\n"));
1042     PR_Sleep(20 * PR_TicksPerSecond() / 1000); /* sleep for 20 ms/iteration */
1043     if (PR_Now() - entryTime > NS_DND_TIMEOUT) break;
1044     gtk_main_iteration();
1045   }
1046   MOZ_LOG(sDragLm, LogLevel::Debug, ("finished inner iteration\n"));
1047 }
1048 
TargetResetData(void)1049 void nsDragService::TargetResetData(void) {
1050   mTargetDragDataReceived = false;
1051   // make sure to free old data if we have to
1052   g_free(mTargetDragData);
1053   mTargetDragData = 0;
1054   mTargetDragDataLen = 0;
1055 }
1056 
GetSourceList(void)1057 GtkTargetList *nsDragService::GetSourceList(void) {
1058   if (!mSourceDataItems) return nullptr;
1059   nsTArray<GtkTargetEntry *> targetArray;
1060   GtkTargetEntry *targets;
1061   GtkTargetList *targetList = 0;
1062   uint32_t targetCount = 0;
1063   unsigned int numDragItems = 0;
1064 
1065   mSourceDataItems->GetLength(&numDragItems);
1066 
1067   // Check to see if we're dragging > 1 item.
1068   if (numDragItems > 1) {
1069     // as the Xdnd protocol only supports a single item (or is it just
1070     // gtk's implementation?), we don't advertise all flavours listed
1071     // in the nsITransferable.
1072 
1073     // the application/x-moz-internal-item-list format, which preserves
1074     // all information for drags within the same mozilla instance.
1075     GtkTargetEntry *listTarget =
1076         (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
1077     listTarget->target = g_strdup(gMimeListType);
1078     listTarget->flags = 0;
1079     MOZ_LOG(sDragLm, LogLevel::Debug,
1080             ("automatically adding target %s\n", listTarget->target));
1081     targetArray.AppendElement(listTarget);
1082 
1083     // check what flavours are supported so we can decide what other
1084     // targets to advertise.
1085     nsCOMPtr<nsITransferable> currItem = do_QueryElementAt(mSourceDataItems, 0);
1086 
1087     if (currItem) {
1088       nsCOMPtr<nsIArray> flavorList;
1089       currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
1090       if (flavorList) {
1091         uint32_t numFlavors;
1092         flavorList->GetLength(&numFlavors);
1093         for (uint32_t flavorIndex = 0; flavorIndex < numFlavors;
1094              ++flavorIndex) {
1095           nsCOMPtr<nsISupportsCString> currentFlavor;
1096           currentFlavor = do_QueryElementAt(flavorList, flavorIndex);
1097           if (currentFlavor) {
1098             nsCString flavorStr;
1099             currentFlavor->ToString(getter_Copies(flavorStr));
1100 
1101             // check if text/x-moz-url is supported.
1102             // If so, advertise
1103             // text/uri-list.
1104             if (flavorStr.EqualsLiteral(kURLMime)) {
1105               listTarget = (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
1106               listTarget->target = g_strdup(gTextUriListType);
1107               listTarget->flags = 0;
1108               MOZ_LOG(sDragLm, LogLevel::Debug,
1109                       ("automatically adding target %s\n", listTarget->target));
1110               targetArray.AppendElement(listTarget);
1111             }
1112           }
1113         }  // foreach flavor in item
1114       }    // if valid flavor list
1115     }      // if item is a transferable
1116   } else if (numDragItems == 1) {
1117     nsCOMPtr<nsITransferable> currItem = do_QueryElementAt(mSourceDataItems, 0);
1118     if (currItem) {
1119       nsCOMPtr<nsIArray> flavorList;
1120       currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
1121       if (flavorList) {
1122         uint32_t numFlavors;
1123         flavorList->GetLength(&numFlavors);
1124         for (uint32_t flavorIndex = 0; flavorIndex < numFlavors;
1125              ++flavorIndex) {
1126           nsCOMPtr<nsISupportsCString> currentFlavor;
1127           currentFlavor = do_QueryElementAt(flavorList, flavorIndex);
1128           if (currentFlavor) {
1129             nsCString flavorStr;
1130             currentFlavor->ToString(getter_Copies(flavorStr));
1131             GtkTargetEntry *target =
1132                 (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
1133             target->target = g_strdup(flavorStr.get());
1134             target->flags = 0;
1135             MOZ_LOG(sDragLm, LogLevel::Debug,
1136                     ("adding target %s\n", target->target));
1137             targetArray.AppendElement(target);
1138 
1139             // If there is a file, add the text/uri-list type.
1140             if (flavorStr.EqualsLiteral(kFileMime)) {
1141               GtkTargetEntry *urilistTarget =
1142                   (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
1143               urilistTarget->target = g_strdup(gTextUriListType);
1144               urilistTarget->flags = 0;
1145               MOZ_LOG(
1146                   sDragLm, LogLevel::Debug,
1147                   ("automatically adding target %s\n", urilistTarget->target));
1148               targetArray.AppendElement(urilistTarget);
1149             }
1150             // Check to see if this is text/unicode.
1151             // If it is, add text/plain
1152             // since we automatically support text/plain
1153             // if we support text/unicode.
1154             else if (flavorStr.EqualsLiteral(kUnicodeMime)) {
1155               GtkTargetEntry *plainUTF8Target =
1156                   (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
1157               plainUTF8Target->target = g_strdup(gTextPlainUTF8Type);
1158               plainUTF8Target->flags = 0;
1159               MOZ_LOG(sDragLm, LogLevel::Debug,
1160                       ("automatically adding target %s\n",
1161                        plainUTF8Target->target));
1162               targetArray.AppendElement(plainUTF8Target);
1163 
1164               GtkTargetEntry *plainTarget =
1165                   (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
1166               plainTarget->target = g_strdup(kTextMime);
1167               plainTarget->flags = 0;
1168               MOZ_LOG(
1169                   sDragLm, LogLevel::Debug,
1170                   ("automatically adding target %s\n", plainTarget->target));
1171               targetArray.AppendElement(plainTarget);
1172             }
1173             // Check to see if this is the x-moz-url type.
1174             // If it is, add _NETSCAPE_URL
1175             // this is a type used by everybody.
1176             else if (flavorStr.EqualsLiteral(kURLMime)) {
1177               GtkTargetEntry *urlTarget =
1178                   (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
1179               urlTarget->target = g_strdup(gMozUrlType);
1180               urlTarget->flags = 0;
1181               MOZ_LOG(sDragLm, LogLevel::Debug,
1182                       ("automatically adding target %s\n", urlTarget->target));
1183               targetArray.AppendElement(urlTarget);
1184             }
1185           }
1186         }  // foreach flavor in item
1187       }    // if valid flavor list
1188     }      // if item is a transferable
1189   }        // if it is a single item drag
1190 
1191   // get all the elements that we created.
1192   targetCount = targetArray.Length();
1193   if (targetCount) {
1194     // allocate space to create the list of valid targets
1195     targets = (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry) * targetCount);
1196     uint32_t targetIndex;
1197     for (targetIndex = 0; targetIndex < targetCount; ++targetIndex) {
1198       GtkTargetEntry *disEntry = targetArray.ElementAt(targetIndex);
1199       // this is a string reference but it will be freed later.
1200       targets[targetIndex].target = disEntry->target;
1201       targets[targetIndex].flags = disEntry->flags;
1202       targets[targetIndex].info = 0;
1203     }
1204     targetList = gtk_target_list_new(targets, targetCount);
1205     // clean up the target list
1206     for (uint32_t cleanIndex = 0; cleanIndex < targetCount; ++cleanIndex) {
1207       GtkTargetEntry *thisTarget = targetArray.ElementAt(cleanIndex);
1208       g_free(thisTarget->target);
1209       g_free(thisTarget);
1210     }
1211     g_free(targets);
1212   }
1213   return targetList;
1214 }
1215 
SourceEndDragSession(GdkDragContext * aContext,gint aResult)1216 void nsDragService::SourceEndDragSession(GdkDragContext *aContext,
1217                                          gint aResult) {
1218   // this just releases the list of data items that we provide
1219   mSourceDataItems = nullptr;
1220 
1221   if (!mDoingDrag || mScheduledTask == eDragTaskSourceEnd)
1222     // EndDragSession() was already called on drop
1223     // or SourceEndDragSession on drag-failed
1224     return;
1225 
1226   if (mEndDragPoint.x < 0) {
1227     // We don't have a drag end point, so guess
1228     gint x, y;
1229     GdkDisplay *display = gdk_display_get_default();
1230     if (display) {
1231       gint scale = ScreenHelperGTK::GetGTKMonitorScaleFactor();
1232       gdk_display_get_pointer(display, nullptr, &x, &y, nullptr);
1233       SetDragEndPoint(LayoutDeviceIntPoint(x * scale, y * scale));
1234     }
1235   }
1236 
1237   // Either the drag was aborted or the drop occurred outside the app.
1238   // The dropEffect of mDataTransfer is not updated for motion outside the
1239   // app, but is needed for the dragend event, so set it now.
1240 
1241   uint32_t dropEffect;
1242 
1243   if (aResult == MOZ_GTK_DRAG_RESULT_SUCCESS) {
1244     // With GTK+ versions 2.10.x and prior the drag may have been
1245     // cancelled (but no drag-failed signal would have been sent).
1246     // aContext->dest_window will be non-nullptr only if the drop was
1247     // sent.
1248     GdkDragAction action = gdk_drag_context_get_dest_window(aContext)
1249                                ? gdk_drag_context_get_actions(aContext)
1250                                : (GdkDragAction)0;
1251 
1252     // Only one bit of action should be set, but, just in case someone
1253     // does something funny, erring away from MOVE, and not recording
1254     // unusual action combinations as NONE.
1255     if (!action)
1256       dropEffect = DRAGDROP_ACTION_NONE;
1257     else if (action & GDK_ACTION_COPY)
1258       dropEffect = DRAGDROP_ACTION_COPY;
1259     else if (action & GDK_ACTION_LINK)
1260       dropEffect = DRAGDROP_ACTION_LINK;
1261     else if (action & GDK_ACTION_MOVE)
1262       dropEffect = DRAGDROP_ACTION_MOVE;
1263     else
1264       dropEffect = DRAGDROP_ACTION_COPY;
1265 
1266   } else {
1267     dropEffect = DRAGDROP_ACTION_NONE;
1268 
1269     if (aResult != MOZ_GTK_DRAG_RESULT_NO_TARGET) {
1270       mUserCancelled = true;
1271     }
1272   }
1273 
1274   if (mDataTransfer) {
1275     mDataTransfer->SetDropEffectInt(dropEffect);
1276   }
1277 
1278   // Schedule the appropriate drag end dom events.
1279   Schedule(eDragTaskSourceEnd, nullptr, nullptr, LayoutDeviceIntPoint(), 0);
1280 }
1281 
CreateUriList(nsIArray * items,gchar ** text,gint * length)1282 static void CreateUriList(nsIArray *items, gchar **text, gint *length) {
1283   uint32_t i, count;
1284   GString *uriList = g_string_new(nullptr);
1285 
1286   items->GetLength(&count);
1287   for (i = 0; i < count; i++) {
1288     nsCOMPtr<nsITransferable> item;
1289     item = do_QueryElementAt(items, i);
1290 
1291     if (item) {
1292       uint32_t tmpDataLen = 0;
1293       void *tmpData = nullptr;
1294       nsresult rv = NS_OK;
1295       nsCOMPtr<nsISupports> data;
1296       rv = item->GetTransferData(kURLMime, getter_AddRefs(data), &tmpDataLen);
1297 
1298       if (NS_SUCCEEDED(rv)) {
1299         nsPrimitiveHelpers::CreateDataFromPrimitive(
1300             nsDependentCString(kURLMime), data, &tmpData, tmpDataLen);
1301         char *plainTextData = nullptr;
1302         char16_t *castedUnicode = reinterpret_cast<char16_t *>(tmpData);
1303         uint32_t plainTextLen = 0;
1304         UTF16ToNewUTF8(castedUnicode, tmpDataLen / 2, &plainTextData,
1305                        &plainTextLen);
1306         if (plainTextData) {
1307           uint32_t j;
1308 
1309           // text/x-moz-url is of form url + "\n" + title.
1310           // We just want the url.
1311           for (j = 0; j < plainTextLen; j++)
1312             if (plainTextData[j] == '\n' || plainTextData[j] == '\r') {
1313               plainTextData[j] = '\0';
1314               break;
1315             }
1316           g_string_append(uriList, plainTextData);
1317           g_string_append(uriList, "\r\n");
1318           // this wasn't allocated with glib
1319           free(plainTextData);
1320         }
1321         if (tmpData) {
1322           // this wasn't allocated with glib
1323           free(tmpData);
1324         }
1325       } else {
1326         // There is no uri available.  If there is a file available,
1327         // create a uri from the file.
1328         nsCOMPtr<nsISupports> data;
1329         rv =
1330             item->GetTransferData(kFileMime, getter_AddRefs(data), &tmpDataLen);
1331         if (NS_SUCCEEDED(rv)) {
1332           nsCOMPtr<nsIFile> file = do_QueryInterface(data);
1333           if (!file) {
1334             // Sometimes the file is wrapped in a
1335             // nsISupportsInterfacePointer. See bug 1310193 for
1336             // removing this distinction.
1337             nsCOMPtr<nsISupportsInterfacePointer> ptr = do_QueryInterface(data);
1338             if (ptr) {
1339               ptr->GetData(getter_AddRefs(data));
1340               file = do_QueryInterface(data);
1341             }
1342           }
1343 
1344           if (file) {
1345             nsCOMPtr<nsIURI> fileURI;
1346             NS_NewFileURI(getter_AddRefs(fileURI), file);
1347             if (fileURI) {
1348               nsAutoCString uristring;
1349               fileURI->GetSpec(uristring);
1350               g_string_append(uriList, uristring.get());
1351               g_string_append(uriList, "\r\n");
1352             }
1353           }
1354         }
1355       }
1356     }
1357   }
1358   *text = uriList->str;
1359   *length = uriList->len + 1;
1360   g_string_free(uriList, FALSE);  // don't free the data
1361 }
1362 
SourceDataGet(GtkWidget * aWidget,GdkDragContext * aContext,GtkSelectionData * aSelectionData,guint32 aTime)1363 void nsDragService::SourceDataGet(GtkWidget *aWidget, GdkDragContext *aContext,
1364                                   GtkSelectionData *aSelectionData,
1365                                   guint32 aTime) {
1366   MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::SourceDataGet"));
1367   GdkAtom target = gtk_selection_data_get_target(aSelectionData);
1368   nsCString mimeFlavor;
1369   gchar *typeName = 0;
1370   typeName = gdk_atom_name(target);
1371   if (!typeName) {
1372     MOZ_LOG(sDragLm, LogLevel::Debug, ("failed to get atom name.\n"));
1373     return;
1374   }
1375 
1376   MOZ_LOG(sDragLm, LogLevel::Debug, ("Type is %s\n", typeName));
1377   // make a copy since |nsCString| won't use |g_free|...
1378   mimeFlavor.Adopt(strdup(typeName));
1379   g_free(typeName);
1380   // check to make sure that we have data items to return.
1381   if (!mSourceDataItems) {
1382     MOZ_LOG(sDragLm, LogLevel::Debug, ("Failed to get our data items\n"));
1383     return;
1384   }
1385 
1386   nsCOMPtr<nsITransferable> item;
1387   item = do_QueryElementAt(mSourceDataItems, 0);
1388   if (item) {
1389     // if someone was asking for text/plain, lookup unicode instead so
1390     // we can convert it.
1391     bool needToDoConversionToPlainText = false;
1392     const char *actualFlavor;
1393     if (mimeFlavor.EqualsLiteral(kTextMime) ||
1394         mimeFlavor.EqualsLiteral(gTextPlainUTF8Type)) {
1395       actualFlavor = kUnicodeMime;
1396       needToDoConversionToPlainText = true;
1397     }
1398     // if someone was asking for _NETSCAPE_URL we need to convert to
1399     // plain text but we also need to look for x-moz-url
1400     else if (mimeFlavor.EqualsLiteral(gMozUrlType)) {
1401       actualFlavor = kURLMime;
1402       needToDoConversionToPlainText = true;
1403     }
1404     // if someone was asking for text/uri-list we need to convert to
1405     // plain text.
1406     else if (mimeFlavor.EqualsLiteral(gTextUriListType)) {
1407       actualFlavor = gTextUriListType;
1408       needToDoConversionToPlainText = true;
1409     } else
1410       actualFlavor = mimeFlavor.get();
1411 
1412     uint32_t tmpDataLen = 0;
1413     void *tmpData = nullptr;
1414     nsresult rv;
1415     nsCOMPtr<nsISupports> data;
1416     rv = item->GetTransferData(actualFlavor, getter_AddRefs(data), &tmpDataLen);
1417     if (NS_SUCCEEDED(rv)) {
1418       nsPrimitiveHelpers::CreateDataFromPrimitive(
1419           nsDependentCString(actualFlavor), data, &tmpData, tmpDataLen);
1420       // if required, do the extra work to convert unicode to plain
1421       // text and replace the output values with the plain text.
1422       if (needToDoConversionToPlainText) {
1423         char *plainTextData = nullptr;
1424         char16_t *castedUnicode = reinterpret_cast<char16_t *>(tmpData);
1425         uint32_t plainTextLen = 0;
1426         UTF16ToNewUTF8(castedUnicode, tmpDataLen / 2, &plainTextData,
1427                        &plainTextLen);
1428         if (tmpData) {
1429           // this was not allocated using glib
1430           free(tmpData);
1431           tmpData = plainTextData;
1432           tmpDataLen = plainTextLen;
1433         }
1434       }
1435       if (tmpData) {
1436         // this copies the data
1437         gtk_selection_data_set(aSelectionData, target, 8, (guchar *)tmpData,
1438                                tmpDataLen);
1439         // this wasn't allocated with glib
1440         free(tmpData);
1441       }
1442     } else {
1443       if (mimeFlavor.EqualsLiteral(gTextUriListType)) {
1444         // fall back for text/uri-list
1445         gchar *uriList;
1446         gint length;
1447         CreateUriList(mSourceDataItems, &uriList, &length);
1448         gtk_selection_data_set(aSelectionData, target, 8, (guchar *)uriList,
1449                                length);
1450         g_free(uriList);
1451         return;
1452       }
1453     }
1454   }
1455 }
1456 
SetDragIcon(GdkDragContext * aContext)1457 void nsDragService::SetDragIcon(GdkDragContext *aContext) {
1458   if (!mHasImage && !mSelection) return;
1459 
1460   LayoutDeviceIntRect dragRect;
1461   nsPresContext *pc;
1462   RefPtr<SourceSurface> surface;
1463   DrawDrag(mSourceNode, mSourceRegion, mScreenPosition, &dragRect, &surface,
1464            &pc);
1465   if (!pc) return;
1466 
1467   LayoutDeviceIntPoint screenPoint =
1468       ConvertToUnscaledDevPixels(pc, mScreenPosition);
1469   int32_t offsetX = screenPoint.x - dragRect.x;
1470   int32_t offsetY = screenPoint.y - dragRect.y;
1471 
1472   // If a popup is set as the drag image, use its widget. Otherwise, use
1473   // the surface that DrawDrag created.
1474   //
1475   // XXX: Disable drag popups on GTK 3.19.4 and above: see bug 1264454.
1476   //      Fix this once a new GTK version ships that does not destroy our
1477   //      widget in gtk_drag_set_icon_widget.
1478   if (mDragPopup && gtk_check_version(3, 19, 4)) {
1479     GtkWidget *gtkWidget = nullptr;
1480     nsIFrame *frame = mDragPopup->GetPrimaryFrame();
1481     if (frame) {
1482       // DrawDrag ensured that this is a popup frame.
1483       nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget();
1484       if (widget) {
1485         gtkWidget = (GtkWidget *)widget->GetNativeData(NS_NATIVE_SHELLWIDGET);
1486         if (gtkWidget) {
1487           OpenDragPopup();
1488           gtk_drag_set_icon_widget(aContext, gtkWidget, offsetX, offsetY);
1489         }
1490       }
1491     }
1492   } else if (surface) {
1493     if (!SetAlphaPixmap(surface, aContext, offsetX, offsetY, dragRect)) {
1494       GdkPixbuf *dragPixbuf = nsImageToPixbuf::SourceSurfaceToPixbuf(
1495           surface, dragRect.width, dragRect.height);
1496       if (dragPixbuf) {
1497         gtk_drag_set_icon_pixbuf(aContext, dragPixbuf, offsetX, offsetY);
1498         g_object_unref(dragPixbuf);
1499       }
1500     }
1501   }
1502 }
1503 
invisibleSourceDragBegin(GtkWidget * aWidget,GdkDragContext * aContext,gpointer aData)1504 static void invisibleSourceDragBegin(GtkWidget *aWidget,
1505                                      GdkDragContext *aContext, gpointer aData) {
1506   MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragBegin"));
1507   nsDragService *dragService = (nsDragService *)aData;
1508 
1509   dragService->SetDragIcon(aContext);
1510 }
1511 
invisibleSourceDragDataGet(GtkWidget * aWidget,GdkDragContext * aContext,GtkSelectionData * aSelectionData,guint aInfo,guint32 aTime,gpointer aData)1512 static void invisibleSourceDragDataGet(GtkWidget *aWidget,
1513                                        GdkDragContext *aContext,
1514                                        GtkSelectionData *aSelectionData,
1515                                        guint aInfo, guint32 aTime,
1516                                        gpointer aData) {
1517   MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragDataGet"));
1518   nsDragService *dragService = (nsDragService *)aData;
1519   dragService->SourceDataGet(aWidget, aContext, aSelectionData, aTime);
1520 }
1521 
invisibleSourceDragFailed(GtkWidget * aWidget,GdkDragContext * aContext,gint aResult,gpointer aData)1522 static gboolean invisibleSourceDragFailed(GtkWidget *aWidget,
1523                                           GdkDragContext *aContext,
1524                                           gint aResult, gpointer aData) {
1525   MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragFailed %i", aResult));
1526   nsDragService *dragService = (nsDragService *)aData;
1527   // End the drag session now (rather than waiting for the drag-end signal)
1528   // so that operations performed on dropEffect == none can start immediately
1529   // rather than waiting for the drag-failed animation to finish.
1530   dragService->SourceEndDragSession(aContext, aResult);
1531 
1532   // We should return TRUE to disable the drag-failed animation iff the
1533   // source performed an operation when dropEffect was none, but the handler
1534   // of the dragend DOM event doesn't provide this information.
1535   return FALSE;
1536 }
1537 
invisibleSourceDragEnd(GtkWidget * aWidget,GdkDragContext * aContext,gpointer aData)1538 static void invisibleSourceDragEnd(GtkWidget *aWidget, GdkDragContext *aContext,
1539                                    gpointer aData) {
1540   MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragEnd"));
1541   nsDragService *dragService = (nsDragService *)aData;
1542 
1543   // The drag has ended.  Release the hostages!
1544   dragService->SourceEndDragSession(aContext, MOZ_GTK_DRAG_RESULT_SUCCESS);
1545 }
1546 
1547 // The following methods handle responding to GTK drag signals and
1548 // tracking state between these signals.
1549 //
1550 // In general, GTK does not expect us to run the event loop while handling its
1551 // drag signals, however our drag event handlers may run the
1552 // event loop, most often to fetch information about the drag data.
1553 //
1554 // GTK, for example, uses the return value from drag-motion signals to
1555 // determine whether drag-leave signals should be sent.  If an event loop is
1556 // run during drag-motion the XdndLeave message can get processed but when GTK
1557 // receives the message it does not yet know that it needs to send the
1558 // drag-leave signal to our widget.
1559 //
1560 // After a drag-drop signal, we need to reply with gtk_drag_finish().
1561 // However, gtk_drag_finish should happen after the drag-drop signal handler
1562 // returns so that when the Motif drag protocol is used, the
1563 // XmTRANSFER_SUCCESS during gtk_drag_finish is sent after the XmDROP_START
1564 // reply sent on return from the drag-drop signal handler.
1565 //
1566 // Similarly drag-end for a successful drag and drag-failed are not good
1567 // times to run a nested event loop as gtk_drag_drop_finished() and
1568 // gtk_drag_source_info_destroy() don't gtk_drag_clear_source_info() or remove
1569 // drop_timeout until after at least the first of these signals is sent.
1570 // Processing other events (e.g. a slow GDK_DROP_FINISHED reply, or the drop
1571 // timeout) could cause gtk_drag_drop_finished to be called again with the
1572 // same GtkDragSourceInfo, which won't like being destroyed twice.
1573 //
1574 // Therefore we reply to the signals immediately and schedule a task to
1575 // dispatch the Gecko events, which may run the event loop.
1576 //
1577 // Action in response to drag-leave signals is also delayed until the event
1578 // loop runs again so that we find out whether a drag-drop signal follows.
1579 //
1580 // A single task is scheduled to manage responses to all three GTK signals.
1581 // If further signals are received while the task is scheduled, the scheduled
1582 // response is updated, sometimes effectively compressing successive signals.
1583 //
1584 // No Gecko drag events are dispatched (during nested event loops) while other
1585 // Gecko drag events are in flight.  This helps event handlers that may not
1586 // expect nested events, while accessing an event's dataTransfer for example.
1587 
ScheduleMotionEvent(nsWindow * aWindow,GdkDragContext * aDragContext,LayoutDeviceIntPoint aWindowPoint,guint aTime)1588 gboolean nsDragService::ScheduleMotionEvent(nsWindow *aWindow,
1589                                             GdkDragContext *aDragContext,
1590                                             LayoutDeviceIntPoint aWindowPoint,
1591                                             guint aTime) {
1592   if (mScheduledTask == eDragTaskMotion) {
1593     // The drag source has sent another motion message before we've
1594     // replied to the previous.  That shouldn't happen with Xdnd.  The
1595     // spec for Motif drags is less clear, but we'll just update the
1596     // scheduled task with the new position reply only to the most
1597     // recent message.
1598     NS_WARNING("Drag Motion message received before previous reply was sent");
1599   }
1600 
1601   // Returning TRUE means we'll reply with a status message, unless we first
1602   // get a leave.
1603   return Schedule(eDragTaskMotion, aWindow, aDragContext, aWindowPoint, aTime);
1604 }
1605 
ScheduleLeaveEvent()1606 void nsDragService::ScheduleLeaveEvent() {
1607   // We don't know at this stage whether a drop signal will immediately
1608   // follow.  If the drop signal gets sent it will happen before we return
1609   // to the main loop and the scheduled leave task will be replaced.
1610   if (!Schedule(eDragTaskLeave, nullptr, nullptr, LayoutDeviceIntPoint(), 0)) {
1611     NS_WARNING("Drag leave after drop");
1612   }
1613 }
1614 
ScheduleDropEvent(nsWindow * aWindow,GdkDragContext * aDragContext,LayoutDeviceIntPoint aWindowPoint,guint aTime)1615 gboolean nsDragService::ScheduleDropEvent(nsWindow *aWindow,
1616                                           GdkDragContext *aDragContext,
1617                                           LayoutDeviceIntPoint aWindowPoint,
1618                                           guint aTime) {
1619   if (!Schedule(eDragTaskDrop, aWindow, aDragContext, aWindowPoint, aTime)) {
1620     NS_WARNING("Additional drag drop ignored");
1621     return FALSE;
1622   }
1623 
1624   SetDragEndPoint(aWindowPoint + aWindow->WidgetToScreenOffset());
1625 
1626   // We'll reply with gtk_drag_finish().
1627   return TRUE;
1628 }
1629 
Schedule(DragTask aTask,nsWindow * aWindow,GdkDragContext * aDragContext,LayoutDeviceIntPoint aWindowPoint,guint aTime)1630 gboolean nsDragService::Schedule(DragTask aTask, nsWindow *aWindow,
1631                                  GdkDragContext *aDragContext,
1632                                  LayoutDeviceIntPoint aWindowPoint,
1633                                  guint aTime) {
1634   // If there is an existing leave or motion task scheduled, then that
1635   // will be replaced.  When the new task is run, it will dispatch
1636   // any necessary leave or motion events.
1637 
1638   // If aTask is eDragTaskSourceEnd, then it will replace even a scheduled
1639   // drop event (which could happen if the drop event has not been processed
1640   // within the allowed time).  Otherwise, if we haven't yet run a scheduled
1641   // drop or end task, just say that we are not ready to receive another
1642   // drop.
1643   if (mScheduledTask == eDragTaskSourceEnd ||
1644       (mScheduledTask == eDragTaskDrop && aTask != eDragTaskSourceEnd))
1645     return FALSE;
1646 
1647   mScheduledTask = aTask;
1648   mPendingWindow = aWindow;
1649   mPendingDragContext = aDragContext;
1650   mPendingWindowPoint = aWindowPoint;
1651   mPendingTime = aTime;
1652 
1653   if (!mTaskSource) {
1654     // High priority is used here because the native events involved have
1655     // already waited at default priority.  Perhaps a lower than default
1656     // priority could be used for motion tasks because there is a chance
1657     // that a leave or drop is waiting, but managing different priorities
1658     // may not be worth the effort.  Motion tasks shouldn't queue up as
1659     // they should be throttled based on replies.
1660     mTaskSource =
1661         g_idle_add_full(G_PRIORITY_HIGH, TaskDispatchCallback, this, nullptr);
1662   }
1663   return TRUE;
1664 }
1665 
TaskDispatchCallback(gpointer data)1666 gboolean nsDragService::TaskDispatchCallback(gpointer data) {
1667   RefPtr<nsDragService> dragService = static_cast<nsDragService *>(data);
1668   return dragService->RunScheduledTask();
1669 }
1670 
RunScheduledTask()1671 gboolean nsDragService::RunScheduledTask() {
1672   if (mTargetWindow && mTargetWindow != mPendingWindow) {
1673     MOZ_LOG(sDragLm, LogLevel::Debug,
1674             ("nsDragService: dispatch drag leave (%p)\n", mTargetWindow.get()));
1675     mTargetWindow->DispatchDragEvent(eDragExit, mTargetWindowPoint, 0);
1676 
1677     if (!mSourceNode) {
1678       // The drag that was initiated in a different app. End the drag
1679       // session, since we're done with it for now (until the user drags
1680       // back into this app).
1681       EndDragSession(false, GetCurrentModifiers());
1682     }
1683   }
1684 
1685   // It is possible that the pending state has been updated during dispatch
1686   // of the leave event.  That's fine.
1687 
1688   // Now we collect the pending state because, from this point on, we want
1689   // to use the same state for all events dispatched.  All state is updated
1690   // so that when other tasks are scheduled during dispatch here, this
1691   // task is considered to have already been run.
1692   bool positionHasChanged = mPendingWindow != mTargetWindow ||
1693                             mPendingWindowPoint != mTargetWindowPoint;
1694   DragTask task = mScheduledTask;
1695   mScheduledTask = eDragTaskNone;
1696   mTargetWindow = mPendingWindow.forget();
1697   mTargetWindowPoint = mPendingWindowPoint;
1698 
1699   if (task == eDragTaskLeave || task == eDragTaskSourceEnd) {
1700     if (task == eDragTaskSourceEnd) {
1701       // Dispatch drag end events.
1702       EndDragSession(true, GetCurrentModifiers());
1703     }
1704 
1705     // Nothing more to do
1706     // Returning false removes the task source from the event loop.
1707     mTaskSource = 0;
1708     return FALSE;
1709   }
1710 
1711   // This may be the start of a destination drag session.
1712   StartDragSession();
1713 
1714   // mTargetWidget may be nullptr if the window has been destroyed.
1715   // (The leave event is not scheduled if a drop task is still scheduled.)
1716   // We still reply appropriately to indicate that the drop will or didn't
1717   // succeeed.
1718   mTargetWidget = mTargetWindow->GetMozContainerWidget();
1719   mTargetDragContext.steal(mPendingDragContext);
1720   mTargetTime = mPendingTime;
1721 
1722   // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
1723   // (as at 27 December 2010) indicates that a "drop" event should only be
1724   // fired (at the current target element) if the current drag operation is
1725   // not none.  The current drag operation will only be set to a non-none
1726   // value during a "dragover" event.
1727   //
1728   // If the user has ended the drag before any dragover events have been
1729   // sent, then the spec recommends skipping the drop (because the current
1730   // drag operation is none).  However, here we assume that, by releasing
1731   // the mouse button, the user has indicated that they want to drop, so we
1732   // proceed with the drop where possible.
1733   //
1734   // In order to make the events appear to content in the same way as if the
1735   // spec is being followed we make sure to dispatch a "dragover" event with
1736   // appropriate coordinates and check canDrop before the "drop" event.
1737   //
1738   // When the Xdnd protocol is used for source/destination communication (as
1739   // should be the case with GTK source applications) a dragover event
1740   // should have already been sent during the drag-motion signal, which
1741   // would have already been received because XdndDrop messages do not
1742   // contain a position.  However, we can't assume the same when the Motif
1743   // protocol is used.
1744   if (task == eDragTaskMotion || positionHasChanged) {
1745     UpdateDragAction();
1746     TakeDragEventDispatchedToChildProcess();  // Clear the old value.
1747     DispatchMotionEvents();
1748     if (task == eDragTaskMotion) {
1749       if (TakeDragEventDispatchedToChildProcess()) {
1750         mTargetDragContextForRemote = mTargetDragContext;
1751       } else {
1752         // Reply to tell the source whether we can drop and what
1753         // action would be taken.
1754         ReplyToDragMotion(mTargetDragContext);
1755       }
1756     }
1757   }
1758 
1759   if (task == eDragTaskDrop) {
1760     gboolean success = DispatchDropEvent();
1761 
1762     // Perhaps we should set the del parameter to TRUE when the drag
1763     // action is move, but we don't know whether the data was successfully
1764     // transferred.
1765     gtk_drag_finish(mTargetDragContext, success,
1766                     /* del = */ FALSE, mTargetTime);
1767 
1768     // This drag is over, so clear out our reference to the previous
1769     // window.
1770     mTargetWindow = nullptr;
1771     // Make sure to end the drag session. If this drag started in a
1772     // different app, we won't get a drag_end signal to end it from.
1773     EndDragSession(true, GetCurrentModifiers());
1774   }
1775 
1776   // We're done with the drag context.
1777   mTargetWidget = nullptr;
1778   mTargetDragContext = nullptr;
1779 
1780   // If we got another drag signal while running the sheduled task, that
1781   // must have happened while running a nested event loop.  Leave the task
1782   // source on the event loop.
1783   if (mScheduledTask != eDragTaskNone) return TRUE;
1784 
1785   // We have no task scheduled.
1786   // Returning false removes the task source from the event loop.
1787   mTaskSource = 0;
1788   return FALSE;
1789 }
1790 
1791 // This will update the drag action based on the information in the
1792 // drag context.  Gtk gets this from a combination of the key settings
1793 // and what the source is offering.
1794 
UpdateDragAction()1795 void nsDragService::UpdateDragAction() {
1796   // This doesn't look right.  dragSession.dragAction is used by
1797   // nsContentUtils::SetDataTransferInEvent() to set the initial
1798   // dataTransfer.dropEffect, so GdkDragContext::suggested_action would be
1799   // more appropriate.  GdkDragContext::actions should be used to set
1800   // dataTransfer.effectAllowed, which doesn't currently happen with
1801   // external sources.
1802 
1803   // default is to do nothing
1804   int action = nsIDragService::DRAGDROP_ACTION_NONE;
1805   GdkDragAction gdkAction = gdk_drag_context_get_actions(mTargetDragContext);
1806 
1807   // set the default just in case nothing matches below
1808   if (gdkAction & GDK_ACTION_DEFAULT)
1809     action = nsIDragService::DRAGDROP_ACTION_MOVE;
1810 
1811   // first check to see if move is set
1812   if (gdkAction & GDK_ACTION_MOVE)
1813     action = nsIDragService::DRAGDROP_ACTION_MOVE;
1814 
1815   // then fall to the others
1816   else if (gdkAction & GDK_ACTION_LINK)
1817     action = nsIDragService::DRAGDROP_ACTION_LINK;
1818 
1819   // copy is ctrl
1820   else if (gdkAction & GDK_ACTION_COPY)
1821     action = nsIDragService::DRAGDROP_ACTION_COPY;
1822 
1823   // update the drag information
1824   SetDragAction(action);
1825 }
1826 
1827 NS_IMETHODIMP
UpdateDragEffect()1828 nsDragService::UpdateDragEffect() {
1829   if (mTargetDragContextForRemote) {
1830     ReplyToDragMotion(mTargetDragContextForRemote);
1831     mTargetDragContextForRemote = nullptr;
1832   }
1833   return NS_OK;
1834 }
1835 
DispatchMotionEvents()1836 void nsDragService::DispatchMotionEvents() {
1837   mCanDrop = false;
1838 
1839   FireDragEventAtSource(eDrag, GetCurrentModifiers());
1840 
1841   mTargetWindow->DispatchDragEvent(eDragOver, mTargetWindowPoint, mTargetTime);
1842 }
1843 
1844 // Returns true if the drop was successful
DispatchDropEvent()1845 gboolean nsDragService::DispatchDropEvent() {
1846   // We need to check IsDestroyed here because the nsRefPtr
1847   // only protects this from being deleted, it does NOT protect
1848   // against nsView::~nsView() calling Destroy() on it, bug 378273.
1849   if (mTargetWindow->IsDestroyed()) return FALSE;
1850 
1851   EventMessage msg = mCanDrop ? eDrop : eDragExit;
1852 
1853   mTargetWindow->DispatchDragEvent(msg, mTargetWindowPoint, mTargetTime);
1854 
1855   return mCanDrop;
1856 }
1857 
GetCurrentModifiers()1858 /* static */ uint32_t nsDragService::GetCurrentModifiers() {
1859   return mozilla::widget::KeymapWrapper::ComputeCurrentKeyModifiers();
1860 }
1861