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