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