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