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