summaryrefslogtreecommitdiff
path: root/widget/gtk/nsDragService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/gtk/nsDragService.cpp')
-rw-r--r--widget/gtk/nsDragService.cpp2109
1 files changed, 2109 insertions, 0 deletions
diff --git a/widget/gtk/nsDragService.cpp b/widget/gtk/nsDragService.cpp
new file mode 100644
index 0000000000..15b4eeffa8
--- /dev/null
+++ b/widget/gtk/nsDragService.cpp
@@ -0,0 +1,2109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDragService.h"
+#include "nsArrayUtils.h"
+#include "nsIObserverService.h"
+#include "nsWidgetsCID.h"
+#include "nsWindow.h"
+#include "nsIServiceManager.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIIOService.h"
+#include "nsIFileURL.h"
+#include "nsNetUtil.h"
+#include "mozilla/Logging.h"
+#include "nsTArray.h"
+#include "nsPrimitiveHelpers.h"
+#include "prtime.h"
+#include "prthread.h"
+#include <dlfcn.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include "nsCRT.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Services.h"
+
+#include "gfxXlibSurface.h"
+#include "gfxContext.h"
+#include "nsImageToPixbuf.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsISelection.h"
+#include "nsViewManager.h"
+#include "nsIFrame.h"
+#include "nsGtkUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxPlatform.h"
+#include "nsScreenGtk.h"
+#include "nsArrayUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+// This sets how opaque the drag image is
+#define DRAG_IMAGE_ALPHA_LEVEL 0.5
+
+// These values are copied from GtkDragResult (rather than using GtkDragResult
+// directly) so that this code can be compiled against versions of GTK+ that
+// do not have GtkDragResult.
+// GtkDragResult is available from GTK+ version 2.12.
+enum {
+ MOZ_GTK_DRAG_RESULT_SUCCESS,
+ MOZ_GTK_DRAG_RESULT_NO_TARGET
+};
+
+static PRLogModuleInfo *sDragLm = nullptr;
+
+// data used for synthetic periodic motion events sent to the source widget
+// grabbing real events for the drag.
+static guint sMotionEventTimerID;
+static GdkEvent *sMotionEvent;
+static GtkWidget *sGrabWidget;
+
+static const char gMimeListType[] = "application/x-moz-internal-item-list";
+static const char gMozUrlType[] = "_NETSCAPE_URL";
+static const char gTextUriListType[] = "text/uri-list";
+static const char gTextPlainUTF8Type[] = "text/plain;charset=utf-8";
+
+static void
+invisibleSourceDragBegin(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gpointer aData);
+
+static void
+invisibleSourceDragEnd(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gpointer aData);
+
+static gboolean
+invisibleSourceDragFailed(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gint aResult,
+ gpointer aData);
+
+static void
+invisibleSourceDragDataGet(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ GtkSelectionData *aSelectionData,
+ guint aInfo,
+ guint32 aTime,
+ gpointer aData);
+
+nsDragService::nsDragService()
+ : mScheduledTask(eDragTaskNone)
+ , mTaskSource(0)
+{
+ // We have to destroy the hidden widget before the event loop stops
+ // running.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ obsServ->AddObserver(this, "quit-application", false);
+
+ // our hidden source widget
+#if (MOZ_WIDGET_GTK == 2)
+ mHiddenWidget = gtk_window_new(GTK_WINDOW_POPUP);
+#else
+ // Using an offscreen window works around bug 983843.
+ mHiddenWidget = gtk_offscreen_window_new();
+#endif
+ // make sure that the widget is realized so that
+ // we can use it as a drag source.
+ gtk_widget_realize(mHiddenWidget);
+ // hook up our internal signals so that we can get some feedback
+ // from our drag source
+ g_signal_connect(mHiddenWidget, "drag_begin",
+ G_CALLBACK(invisibleSourceDragBegin), this);
+ g_signal_connect(mHiddenWidget, "drag_data_get",
+ G_CALLBACK(invisibleSourceDragDataGet), this);
+ g_signal_connect(mHiddenWidget, "drag_end",
+ G_CALLBACK(invisibleSourceDragEnd), this);
+ // drag-failed is available from GTK+ version 2.12
+ guint dragFailedID = g_signal_lookup("drag-failed",
+ G_TYPE_FROM_INSTANCE(mHiddenWidget));
+ if (dragFailedID) {
+ g_signal_connect_closure_by_id(mHiddenWidget, dragFailedID, 0,
+ g_cclosure_new(G_CALLBACK(invisibleSourceDragFailed),
+ this, nullptr),
+ FALSE);
+ }
+
+ // set up our logging module
+ if (!sDragLm)
+ sDragLm = PR_NewLogModule("nsDragService");
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::nsDragService"));
+ mCanDrop = false;
+ mTargetDragDataReceived = false;
+ mTargetDragData = 0;
+ mTargetDragDataLen = 0;
+}
+
+nsDragService::~nsDragService()
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::~nsDragService"));
+ if (mTaskSource)
+ g_source_remove(mTaskSource);
+
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsDragService, nsBaseDragService, nsIObserver)
+
+/* static */ nsDragService*
+nsDragService::GetInstance()
+{
+ static const nsIID iid = NS_DRAGSERVICE_CID;
+ nsCOMPtr<nsIDragService> dragService = do_GetService(iid);
+ return static_cast<nsDragService*>(dragService.get());
+ // We rely on XPCOM keeping a reference to the service.
+}
+
+// nsIObserver
+
+NS_IMETHODIMP
+nsDragService::Observe(nsISupports *aSubject, const char *aTopic,
+ const char16_t *aData)
+{
+ if (!nsCRT::strcmp(aTopic, "quit-application")) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService::Observe(\"quit-application\")"));
+ if (mHiddenWidget) {
+ gtk_widget_destroy(mHiddenWidget);
+ mHiddenWidget = 0;
+ }
+ TargetResetData();
+ } else {
+ NS_NOTREACHED("unexpected topic");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+// Support for periodic drag events
+
+// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
+// and the Xdnd protocol both recommend that drag events are sent periodically,
+// but GTK does not normally provide this.
+//
+// Here GTK is periodically stimulated by copies of the most recent mouse
+// motion events so as to send drag position messages to the destination when
+// appropriate (after it has received a status event from the previous
+// message).
+//
+// (If events were sent only on the destination side then the destination
+// would have no message to which it could reply with a drag status. Without
+// sending a drag status to the source, the destination would not be able to
+// change its feedback re whether it could accept the drop, and so the
+// source's behavior on drop will not be consistent.)
+
+static gboolean
+DispatchMotionEventCopy(gpointer aData)
+{
+ // Clear the timer id before OnSourceGrabEventAfter is called during event
+ // dispatch.
+ sMotionEventTimerID = 0;
+
+ GdkEvent *event = sMotionEvent;
+ sMotionEvent = nullptr;
+ // If there is no longer a grab on the widget, then the drag is over and
+ // there is no need to continue drag motion.
+ if (gtk_widget_has_grab(sGrabWidget)) {
+ gtk_propagate_event(sGrabWidget, event);
+ }
+ gdk_event_free(event);
+
+ // Cancel this timer;
+ // We've already started another if the motion event was dispatched.
+ return FALSE;
+}
+
+static void
+OnSourceGrabEventAfter(GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ // If there is no longer a grab on the widget, then the drag motion is
+ // over (though the data may not be fetched yet).
+ if (!gtk_widget_has_grab(sGrabWidget))
+ return;
+
+ if (event->type == GDK_MOTION_NOTIFY) {
+ if (sMotionEvent) {
+ gdk_event_free(sMotionEvent);
+ }
+ sMotionEvent = gdk_event_copy(event);
+
+ // Update the cursor position. The last of these recorded gets used for
+ // the eDragEnd event.
+ nsDragService *dragService = static_cast<nsDragService*>(user_data);
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ auto p = LayoutDeviceIntPoint::Round(event->motion.x_root * scale,
+ event->motion.y_root * scale);
+ dragService->SetDragEndPoint(p);
+ } else if (sMotionEvent && (event->type == GDK_KEY_PRESS ||
+ event->type == GDK_KEY_RELEASE)) {
+ // Update modifier state from key events.
+ sMotionEvent->motion.state = event->key.state;
+ } else {
+ return;
+ }
+
+ if (sMotionEventTimerID) {
+ g_source_remove(sMotionEventTimerID);
+ }
+
+ // G_PRIORITY_DEFAULT_IDLE is lower priority than GDK's redraw idle source
+ // and lower than GTK's idle source that sends drag position messages after
+ // motion-notify signals.
+ //
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
+ // recommends an interval of 350ms +/- 200ms.
+ sMotionEventTimerID =
+ g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, 350,
+ DispatchMotionEventCopy, nullptr, nullptr);
+}
+
+static GtkWindow*
+GetGtkWindow(nsIDOMDocument *aDocument)
+{
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDocument);
+ if (!doc)
+ return nullptr;
+
+ nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
+ if (!presShell)
+ return nullptr;
+
+ RefPtr<nsViewManager> vm = presShell->GetViewManager();
+ if (!vm)
+ return nullptr;
+
+ nsCOMPtr<nsIWidget> widget;
+ vm->GetRootWidget(getter_AddRefs(widget));
+ if (!widget)
+ return nullptr;
+
+ GtkWidget *gtkWidget =
+ static_cast<nsWindow*>(widget.get())->GetMozContainerWidget();
+ if (!gtkWidget)
+ return nullptr;
+
+ GtkWidget *toplevel = nullptr;
+ toplevel = gtk_widget_get_toplevel(gtkWidget);
+ if (!GTK_IS_WINDOW(toplevel))
+ return nullptr;
+
+ return GTK_WINDOW(toplevel);
+}
+
+// nsIDragService
+
+NS_IMETHODIMP
+nsDragService::InvokeDragSession(nsIDOMNode *aDOMNode,
+ nsIArray * aArrayTransferables,
+ nsIScriptableRegion * aRegion,
+ uint32_t aActionType,
+ nsContentPolicyType aContentPolicyType =
+ nsIContentPolicy::TYPE_OTHER)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::InvokeDragSession"));
+
+ // If the previous source drag has not yet completed, signal handlers need
+ // to be removed from sGrabWidget and dragend needs to be dispatched to
+ // the source node, but we can't call EndDragSession yet because we don't
+ // know whether or not the drag succeeded.
+ if (mSourceNode)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ return nsBaseDragService::InvokeDragSession(aDOMNode, aArrayTransferables,
+ aRegion, aActionType,
+ aContentPolicyType);
+}
+
+// nsBaseDragService
+nsresult
+nsDragService::InvokeDragSessionImpl(nsIArray* aArrayTransferables,
+ nsIScriptableRegion* aRegion,
+ uint32_t aActionType)
+{
+ // make sure that we have an array of transferables to use
+ if (!aArrayTransferables)
+ return NS_ERROR_INVALID_ARG;
+ // set our reference to the transferables. this will also addref
+ // the transferables since we're going to hang onto this beyond the
+ // length of this call
+ mSourceDataItems = aArrayTransferables;
+ // get the list of items we offer for drags
+ GtkTargetList *sourceList = GetSourceList();
+
+ if (!sourceList)
+ return NS_OK;
+
+ // stored temporarily until the drag-begin signal has been received
+ mSourceRegion = aRegion;
+
+ // save our action type
+ GdkDragAction action = GDK_ACTION_DEFAULT;
+
+ if (aActionType & DRAGDROP_ACTION_COPY)
+ action = (GdkDragAction)(action | GDK_ACTION_COPY);
+ if (aActionType & DRAGDROP_ACTION_MOVE)
+ action = (GdkDragAction)(action | GDK_ACTION_MOVE);
+ if (aActionType & DRAGDROP_ACTION_LINK)
+ action = (GdkDragAction)(action | GDK_ACTION_LINK);
+
+ // Create a fake event for the drag so we can pass the time (so to speak).
+ // If we don't do this, then, when the timestamp for the pending button
+ // release event is used for the ungrab, the ungrab can fail due to the
+ // timestamp being _earlier_ than CurrentTime.
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+ event.type = GDK_BUTTON_PRESS;
+ event.button.window = gtk_widget_get_window(mHiddenWidget);
+ event.button.time = nsWindow::GetLastUserInputTime();
+
+ // Put the drag widget in the window group of the source node so that the
+ // gtk_grab_add during gtk_drag_begin is effective.
+ // gtk_window_get_group(nullptr) returns the default window group.
+ GtkWindowGroup *window_group =
+ gtk_window_get_group(GetGtkWindow(mSourceDocument));
+ gtk_window_group_add_window(window_group,
+ GTK_WINDOW(mHiddenWidget));
+
+#if (MOZ_WIDGET_GTK == 3)
+ // Get device for event source
+ GdkDisplay *display = gdk_display_get_default();
+ GdkDeviceManager *device_manager = gdk_display_get_device_manager(display);
+ event.button.device = gdk_device_manager_get_client_pointer(device_manager);
+#endif
+
+ // start our drag.
+ GdkDragContext *context = gtk_drag_begin(mHiddenWidget,
+ sourceList,
+ action,
+ 1,
+ &event);
+
+ mSourceRegion = nullptr;
+
+ nsresult rv;
+ if (context) {
+ StartDragSession();
+
+ // GTK uses another hidden window for receiving mouse events.
+ sGrabWidget = gtk_window_group_get_current_grab(window_group);
+ if (sGrabWidget) {
+ g_object_ref(sGrabWidget);
+ // Only motion and key events are required but connect to
+ // "event-after" as this is never blocked by other handlers.
+ g_signal_connect(sGrabWidget, "event-after",
+ G_CALLBACK(OnSourceGrabEventAfter), this);
+ }
+ // We don't have a drag end point yet.
+ mEndDragPoint = LayoutDeviceIntPoint(-1, -1);
+ rv = NS_OK;
+ }
+ else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ gtk_target_list_unref(sourceList);
+
+ return rv;
+}
+
+bool
+nsDragService::SetAlphaPixmap(SourceSurface *aSurface,
+ GdkDragContext *aContext,
+ int32_t aXOffset,
+ int32_t aYOffset,
+ const LayoutDeviceIntRect& dragRect)
+{
+ GdkScreen* screen = gtk_widget_get_screen(mHiddenWidget);
+
+ // Transparent drag icons need, like a lot of transparency-related things,
+ // a compositing X window manager
+ if (!gdk_screen_is_composited(screen))
+ return false;
+
+#if (MOZ_WIDGET_GTK == 2)
+ GdkColormap* alphaColormap = gdk_screen_get_rgba_colormap(screen);
+ if (!alphaColormap)
+ return false;
+
+ GdkPixmap* pixmap = gdk_pixmap_new(nullptr, dragRect.width, dragRect.height,
+ gdk_colormap_get_visual(alphaColormap)->depth);
+ if (!pixmap)
+ return false;
+
+ gdk_drawable_set_colormap(GDK_DRAWABLE(pixmap), alphaColormap);
+
+ // Make a DrawTarget wrapped around the pixmap to render on
+ RefPtr<DrawTarget> dt =
+ nsWindow::GetDrawTargetForGdkDrawable(GDK_DRAWABLE(pixmap),
+ IntSize(dragRect.width,
+ dragRect.height));
+ if (!dt)
+ return false;
+
+ // Clear it...
+ dt->ClearRect(Rect(0, 0, dragRect.width, dragRect.height));
+
+ // ...and paint the drag image with translucency
+ dt->DrawSurface(aSurface,
+ Rect(0, 0, dragRect.width, dragRect.height),
+ Rect(0, 0, dragRect.width, dragRect.height),
+ DrawSurfaceOptions(),
+ DrawOptions(DRAG_IMAGE_ALPHA_LEVEL, CompositionOp::OP_SOURCE));
+
+ // The drag transaction addrefs the pixmap, so we can just unref it from us here
+ gtk_drag_set_icon_pixmap(aContext, alphaColormap, pixmap, nullptr,
+ aXOffset, aYOffset);
+ g_object_unref(pixmap);
+ return true;
+#else
+#ifdef cairo_image_surface_create
+#error "Looks like we're including Mozilla's cairo instead of system cairo"
+#endif
+ // Prior to GTK 3.9.12, cairo surfaces passed into gtk_drag_set_icon_surface
+ // had their shape information derived from the alpha channel and used with
+ // the X SHAPE extension instead of being displayed as an ARGB window.
+ // See bug 1249604.
+ if (gtk_check_version(3, 9, 12))
+ return false;
+
+ // TODO: grab X11 pixmap or image data instead of expensive readback.
+ cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+ dragRect.width,
+ dragRect.height);
+ if (!surf)
+ return false;
+
+ RefPtr<DrawTarget> dt = gfxPlatform::CreateDrawTargetForData(
+ cairo_image_surface_get_data(surf),
+ nsIntSize(dragRect.width, dragRect.height),
+ cairo_image_surface_get_stride(surf),
+ SurfaceFormat::B8G8R8A8);
+ if (!dt)
+ return false;
+
+ dt->ClearRect(Rect(0, 0, dragRect.width, dragRect.height));
+ dt->DrawSurface(aSurface,
+ Rect(0, 0, dragRect.width, dragRect.height),
+ Rect(0, 0, dragRect.width, dragRect.height),
+ DrawSurfaceOptions(),
+ DrawOptions(DRAG_IMAGE_ALPHA_LEVEL, CompositionOp::OP_SOURCE));
+
+ cairo_surface_mark_dirty(surf);
+ cairo_surface_set_device_offset(surf, -aXOffset, -aYOffset);
+
+ // Ensure that the surface is drawn at the correct scale on HiDPI displays.
+ static auto sCairoSurfaceSetDeviceScalePtr =
+ (void (*)(cairo_surface_t*,double,double))
+ dlsym(RTLD_DEFAULT, "cairo_surface_set_device_scale");
+ if (sCairoSurfaceSetDeviceScalePtr) {
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ sCairoSurfaceSetDeviceScalePtr(surf, scale, scale);
+ }
+
+ gtk_drag_set_icon_surface(aContext, surf);
+ cairo_surface_destroy(surf);
+ return true;
+#endif
+}
+
+NS_IMETHODIMP
+nsDragService::StartDragSession()
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::StartDragSession"));
+ return nsBaseDragService::StartDragSession();
+}
+
+NS_IMETHODIMP
+nsDragService::EndDragSession(bool aDoneDrag)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::EndDragSession %d",
+ aDoneDrag));
+
+ if (sGrabWidget) {
+ g_signal_handlers_disconnect_by_func(sGrabWidget,
+ FuncToGpointer(OnSourceGrabEventAfter), this);
+ g_object_unref(sGrabWidget);
+ sGrabWidget = nullptr;
+
+ if (sMotionEventTimerID) {
+ g_source_remove(sMotionEventTimerID);
+ sMotionEventTimerID = 0;
+ }
+ if (sMotionEvent) {
+ gdk_event_free(sMotionEvent);
+ sMotionEvent = nullptr;
+ }
+ }
+
+ // unset our drag action
+ SetDragAction(DRAGDROP_ACTION_NONE);
+
+ // We're done with the drag context.
+ mTargetDragContextForRemote = nullptr;
+
+ return nsBaseDragService::EndDragSession(aDoneDrag);
+}
+
+// nsIDragSession
+NS_IMETHODIMP
+nsDragService::SetCanDrop(bool aCanDrop)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::SetCanDrop %d",
+ aCanDrop));
+ mCanDrop = aCanDrop;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDragService::GetCanDrop(bool *aCanDrop)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetCanDrop"));
+ *aCanDrop = mCanDrop;
+ return NS_OK;
+}
+
+static void
+UTF16ToNewUTF8(const char16_t* aUTF16,
+ uint32_t aUTF16Len,
+ char** aUTF8,
+ uint32_t* aUTF8Len)
+{
+ nsDependentSubstring utf16(aUTF16, aUTF16Len);
+ *aUTF8 = ToNewUTF8String(utf16, aUTF8Len);
+}
+
+static void
+UTF8ToNewUTF16(const char* aUTF8,
+ uint32_t aUTF8Len,
+ char16_t** aUTF16,
+ uint32_t* aUTF16Len)
+{
+ nsDependentCSubstring utf8(aUTF8, aUTF8Len);
+ *aUTF16 = UTF8ToNewUnicode(utf8, aUTF16Len);
+}
+
+// count the number of URIs in some text/uri-list format data.
+static uint32_t
+CountTextUriListItems(const char *data,
+ uint32_t datalen)
+{
+ const char *p = data;
+ const char *endPtr = p + datalen;
+ uint32_t count = 0;
+
+ while (p < endPtr) {
+ // skip whitespace (if any)
+ while (p < endPtr && *p != '\0' && isspace(*p))
+ p++;
+ // if we aren't at the end of the line ...
+ if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r')
+ count++;
+ // skip to the end of the line
+ while (p < endPtr && *p != '\0' && *p != '\n')
+ p++;
+ p++; // skip the actual newline as well.
+ }
+ return count;
+}
+
+// extract an item from text/uri-list formatted data and convert it to
+// unicode.
+static void
+GetTextUriListItem(const char *data,
+ uint32_t datalen,
+ uint32_t aItemIndex,
+ char16_t **convertedText,
+ uint32_t *convertedTextLen)
+{
+ const char *p = data;
+ const char *endPtr = p + datalen;
+ unsigned int count = 0;
+
+ *convertedText = nullptr;
+ while (p < endPtr) {
+ // skip whitespace (if any)
+ while (p < endPtr && *p != '\0' && isspace(*p))
+ p++;
+ // if we aren't at the end of the line, we have a url
+ if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r')
+ count++;
+ // this is the item we are after ...
+ if (aItemIndex + 1 == count) {
+ const char *q = p;
+ while (q < endPtr && *q != '\0' && *q != '\n' && *q != '\r')
+ q++;
+ UTF8ToNewUTF16(p, q - p, convertedText, convertedTextLen);
+ break;
+ }
+ // skip to the end of the line
+ while (p < endPtr && *p != '\0' && *p != '\n')
+ p++;
+ p++; // skip the actual newline as well.
+ }
+
+ // didn't find the desired item, so just pass the whole lot
+ if (!*convertedText) {
+ UTF8ToNewUTF16(data, datalen, convertedText, convertedTextLen);
+ }
+}
+
+NS_IMETHODIMP
+nsDragService::GetNumDropItems(uint32_t * aNumItems)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetNumDropItems"));
+
+ if (!mTargetWidget) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("*** warning: GetNumDropItems \
+ called without a valid target widget!\n"));
+ *aNumItems = 0;
+ return NS_OK;
+ }
+
+ bool isList = IsTargetContextList();
+ if (isList)
+ mSourceDataItems->GetLength(aNumItems);
+ else {
+ GdkAtom gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ const char *data = reinterpret_cast<char*>(mTargetDragData);
+ *aNumItems = CountTextUriListItems(data, mTargetDragDataLen);
+ } else
+ *aNumItems = 1;
+ }
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("%d items", *aNumItems));
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsDragService::GetData(nsITransferable * aTransferable,
+ uint32_t aItemIndex)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetData %d", aItemIndex));
+
+ // make sure that we have a transferable
+ if (!aTransferable)
+ return NS_ERROR_INVALID_ARG;
+
+ if (!mTargetWidget) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("*** warning: GetData \
+ called without a valid target widget!\n"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // get flavor list that includes all acceptable flavors (including
+ // ones obtained through conversion). Flavors are nsISupportsStrings
+ // so that they can be seen from JS.
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(
+ getter_AddRefs(flavorList));
+ if (NS_FAILED(rv))
+ return rv;
+
+ // count the number of flavors
+ uint32_t cnt;
+ flavorList->GetLength(&cnt);
+ unsigned int i;
+
+ // check to see if this is an internal list
+ bool isList = IsTargetContextList();
+
+ if (isList) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("it's a list..."));
+ // find a matching flavor
+ for (i = 0; i < cnt; ++i) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, i);
+ if (!currentFlavor)
+ continue;
+
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ MOZ_LOG(sDragLm,
+ LogLevel::Debug,
+ ("flavor is %s\n", (const char *)flavorStr));
+ // get the item with the right index
+ nsCOMPtr<nsITransferable> item =
+ do_QueryElementAt(mSourceDataItems, aItemIndex);
+ if (!item)
+ continue;
+
+ nsCOMPtr<nsISupports> data;
+ uint32_t tmpDataLen = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("trying to get transfer data for %s\n",
+ (const char *)flavorStr));
+ rv = item->GetTransferData(flavorStr,
+ getter_AddRefs(data),
+ &tmpDataLen);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("failed.\n"));
+ continue;
+ }
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("succeeded.\n"));
+ rv = aTransferable->SetTransferData(flavorStr,data,tmpDataLen);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(sDragLm,
+ LogLevel::Debug,
+ ("fail to set transfer data into transferable!\n"));
+ continue;
+ }
+ // ok, we got the data
+ return NS_OK;
+ }
+ // if we got this far, we failed
+ return NS_ERROR_FAILURE;
+ }
+
+ // Now walk down the list of flavors. When we find one that is
+ // actually present, copy out the data into the transferable in that
+ // format. SetTransferData() implicitly handles conversions.
+ for ( i = 0; i < cnt; ++i ) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, i);
+ if (currentFlavor) {
+ // find our gtk flavor
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ GdkAtom gdkFlavor = gdk_atom_intern(flavorStr, FALSE);
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("looking for data in type %s, gdk flavor %ld\n",
+ static_cast<const char*>(flavorStr), gdkFlavor));
+ bool dataFound = false;
+ if (gdkFlavor) {
+ GetTargetDragData(gdkFlavor);
+ }
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound = true\n"));
+ dataFound = true;
+ }
+ else {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound = false\n"));
+
+ // Dragging and dropping from the file manager would cause us
+ // to parse the source text as a nsIFile URL.
+ if ( strcmp(flavorStr, kFileMime) == 0 ) {
+ gdkFlavor = gdk_atom_intern(kTextMime, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (!mTargetDragData) {
+ gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ }
+ if (mTargetDragData) {
+ const char* text = static_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+
+ GetTextUriListItem(text, mTargetDragDataLen, aItemIndex,
+ &convertedText, &convertedTextLen);
+
+ if (convertedText) {
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ nsCOMPtr<nsIURI> fileURI;
+ rv = ioService->NewURI(NS_ConvertUTF16toUTF8(convertedText),
+ nullptr, nullptr, getter_AddRefs(fileURI));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv)) {
+ // The common wrapping code at the end of
+ // this function assumes the data is text
+ // and calls text-specific operations.
+ // Make a secret hideout here for nsIFile
+ // objects and return early.
+ aTransferable->SetTransferData(flavorStr, file,
+ convertedTextLen);
+ g_free(convertedText);
+ return NS_OK;
+ }
+ }
+ }
+ g_free(convertedText);
+ }
+ continue;
+ }
+ }
+
+ // if we are looking for text/unicode and we fail to find it
+ // on the clipboard first, try again with text/plain. If that
+ // is present, convert it to unicode.
+ if ( strcmp(flavorStr, kUnicodeMime) == 0 ) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/unicode... \
+ trying with text/plain;charset=utf-8\n"));
+ gdkFlavor = gdk_atom_intern(gTextPlainUTF8Type, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Got textplain data\n"));
+ const char* castedText =
+ reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ NS_ConvertUTF8toUTF16 ucs2string(castedText,
+ mTargetDragDataLen);
+ convertedText = ToNewUnicode(ucs2string);
+ if ( convertedText ) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("successfully converted plain text \
+ to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = ucs2string.Length() * 2;
+ dataFound = true;
+ } // if plain text data on clipboard
+ } else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/unicode... \
+ trying again with text/plain\n"));
+ gdkFlavor = gdk_atom_intern(kTextMime, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Got textplain data\n"));
+ const char* castedText =
+ reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+ UTF8ToNewUTF16(castedText, mTargetDragDataLen,
+ &convertedText, &convertedTextLen);
+ if ( convertedText ) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("successfully converted plain text \
+ to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = convertedTextLen * 2;
+ dataFound = true;
+ } // if plain text data on clipboard
+ } // if plain text flavor present
+ } // if plain text charset=utf-8 flavor present
+ } // if looking for text/unicode
+
+ // if we are looking for text/x-moz-url and we failed to find
+ // it on the clipboard, try again with text/uri-list, and then
+ // _NETSCAPE_URL
+ if (strcmp(flavorStr, kURLMime) == 0) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/x-moz-url...\
+ trying again with text/uri-list\n"));
+ gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("Got text/uri-list data\n"));
+ const char *data =
+ reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+
+ GetTextUriListItem(data, mTargetDragDataLen, aItemIndex,
+ &convertedText, &convertedTextLen);
+
+ if ( convertedText ) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("successfully converted \
+ _NETSCAPE_URL to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = convertedTextLen * 2;
+ dataFound = true;
+ }
+ }
+ else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("failed to get text/uri-list data\n"));
+ }
+ if (!dataFound) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/x-moz-url...\
+ trying again with _NETSCAP_URL\n"));
+ gdkFlavor = gdk_atom_intern(gMozUrlType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("Got _NETSCAPE_URL data\n"));
+ const char* castedText =
+ reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+ UTF8ToNewUTF16(castedText, mTargetDragDataLen, &convertedText, &convertedTextLen);
+ if ( convertedText ) {
+ MOZ_LOG(sDragLm,
+ LogLevel::Debug,
+ ("successfully converted _NETSCAPE_URL \
+ to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = convertedTextLen * 2;
+ dataFound = true;
+ }
+ }
+ else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("failed to get _NETSCAPE_URL data\n"));
+ }
+ }
+ }
+
+ } // else we try one last ditch effort to find our data
+
+ if (dataFound) {
+ if (strcmp(flavorStr, kCustomTypesMime) != 0) {
+ // the DOM only wants LF, so convert from MacOS line endings
+ // to DOM line endings.
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(
+ flavorStr,
+ &mTargetDragData,
+ reinterpret_cast<int*>(&mTargetDragDataLen));
+ }
+
+ // put it into the transferable.
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr,
+ mTargetDragData, mTargetDragDataLen,
+ getter_AddRefs(genericDataWrapper));
+ aTransferable->SetTransferData(flavorStr,
+ genericDataWrapper,
+ mTargetDragDataLen);
+ // we found one, get out of this loop!
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound and converted!\n"));
+ break;
+ }
+ } // if (currentFlavor)
+ } // foreach flavor
+
+ return NS_OK;
+
+}
+
+NS_IMETHODIMP
+nsDragService::IsDataFlavorSupported(const char *aDataFlavor,
+ bool *_retval)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::IsDataFlavorSupported %s",
+ aDataFlavor));
+ if (!_retval)
+ return NS_ERROR_INVALID_ARG;
+
+ // set this to no by default
+ *_retval = false;
+
+ // check to make sure that we have a drag object set, here
+ if (!mTargetWidget) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("*** warning: IsDataFlavorSupported \
+ called without a valid target widget!\n"));
+ return NS_OK;
+ }
+
+ // check to see if the target context is a list.
+ bool isList = IsTargetContextList();
+ // if it is, just look in the internal data since we are the source
+ // for it.
+ if (isList) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("It's a list.."));
+ uint32_t numDragItems = 0;
+ // if we don't have mDataItems we didn't start this drag so it's
+ // an external client trying to fool us.
+ if (!mSourceDataItems)
+ return NS_OK;
+ mSourceDataItems->GetLength(&numDragItems);
+ for (uint32_t itemIndex = 0; itemIndex < numDragItems; ++itemIndex) {
+ nsCOMPtr<nsITransferable> currItem =
+ do_QueryElementAt(mSourceDataItems, itemIndex);
+ if (currItem) {
+ nsCOMPtr <nsIArray> flavorList;
+ currItem->FlavorsTransferableCanExport(
+ getter_AddRefs(flavorList));
+ if (flavorList) {
+ uint32_t numFlavors;
+ flavorList->GetLength( &numFlavors );
+ for ( uint32_t flavorIndex = 0;
+ flavorIndex < numFlavors ;
+ ++flavorIndex ) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, flavorIndex);
+ if (currentFlavor) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("checking %s against %s\n",
+ (const char *)flavorStr, aDataFlavor));
+ if (strcmp(flavorStr, aDataFlavor) == 0) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("boioioioiooioioioing!\n"));
+ *_retval = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ // check the target context vs. this flavor, one at a time
+ GList *tmp;
+ for (tmp = gdk_drag_context_list_targets(mTargetDragContext);
+ tmp; tmp = tmp->next) {
+ /* Bug 331198 */
+ GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
+ gchar *name = nullptr;
+ name = gdk_atom_name(atom);
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("checking %s against %s\n", name, aDataFlavor));
+ if (name && (strcmp(name, aDataFlavor) == 0)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("good!\n"));
+ *_retval = true;
+ }
+ // check for automatic text/uri-list -> text/x-moz-url mapping
+ if (!*_retval &&
+ name &&
+ (strcmp(name, gTextUriListType) == 0) &&
+ (strcmp(aDataFlavor, kURLMime) == 0 ||
+ strcmp(aDataFlavor, kFileMime) == 0)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("good! ( it's text/uri-list and \
+ we're checking against text/x-moz-url )\n"));
+ *_retval = true;
+ }
+ // check for automatic _NETSCAPE_URL -> text/x-moz-url mapping
+ if (!*_retval &&
+ name &&
+ (strcmp(name, gMozUrlType) == 0) &&
+ (strcmp(aDataFlavor, kURLMime) == 0)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("good! ( it's _NETSCAPE_URL and \
+ we're checking against text/x-moz-url )\n"));
+ *_retval = true;
+ }
+ // check for auto text/plain -> text/unicode mapping
+ if (!*_retval &&
+ name &&
+ (strcmp(name, kTextMime) == 0) &&
+ ((strcmp(aDataFlavor, kUnicodeMime) == 0) ||
+ (strcmp(aDataFlavor, kFileMime) == 0))) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("good! ( it's text plain and we're checking \
+ against text/unicode or application/x-moz-file)\n"));
+ *_retval = true;
+ }
+ g_free(name);
+ }
+ return NS_OK;
+}
+
+void
+nsDragService::ReplyToDragMotion(GdkDragContext* aDragContext)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService::ReplyToDragMotion %d", mCanDrop));
+
+ GdkDragAction action = (GdkDragAction)0;
+ if (mCanDrop) {
+ // notify the dragger if we can drop
+ switch (mDragAction) {
+ case DRAGDROP_ACTION_COPY:
+ action = GDK_ACTION_COPY;
+ break;
+ case DRAGDROP_ACTION_LINK:
+ action = GDK_ACTION_LINK;
+ break;
+ case DRAGDROP_ACTION_NONE:
+ action = (GdkDragAction)0;
+ break;
+ default:
+ action = GDK_ACTION_MOVE;
+ break;
+ }
+ }
+
+ gdk_drag_status(aDragContext, action, mTargetTime);
+}
+
+void
+nsDragService::TargetDataReceived(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gint aX,
+ gint aY,
+ GtkSelectionData *aSelectionData,
+ guint aInfo,
+ guint32 aTime)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::TargetDataReceived"));
+ TargetResetData();
+ mTargetDragDataReceived = true;
+ gint len = gtk_selection_data_get_length(aSelectionData);
+ const guchar* data = gtk_selection_data_get_data(aSelectionData);
+ if (len > 0 && data) {
+ mTargetDragDataLen = len;
+ mTargetDragData = g_malloc(mTargetDragDataLen);
+ memcpy(mTargetDragData, data, mTargetDragDataLen);
+ }
+ else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("Failed to get data. selection data len was %d\n",
+ mTargetDragDataLen));
+ }
+}
+
+bool
+nsDragService::IsTargetContextList(void)
+{
+ bool retval = false;
+
+ // gMimeListType drags only work for drags within a single process. The
+ // gtk_drag_get_source_widget() function will return nullptr if the source
+ // of the drag is another app, so we use it to check if a gMimeListType
+ // drop will work or not.
+ if (gtk_drag_get_source_widget(mTargetDragContext) == nullptr)
+ return retval;
+
+ GList *tmp;
+
+ // walk the list of context targets and see if one of them is a list
+ // of items.
+ for (tmp = gdk_drag_context_list_targets(mTargetDragContext);
+ tmp; tmp = tmp->next) {
+ /* Bug 331198 */
+ GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
+ gchar *name = nullptr;
+ name = gdk_atom_name(atom);
+ if (name && strcmp(name, gMimeListType) == 0)
+ retval = true;
+ g_free(name);
+ if (retval)
+ break;
+ }
+ return retval;
+}
+
+// Maximum time to wait for a "drag_received" arrived, in microseconds
+#define NS_DND_TIMEOUT 500000
+
+void
+nsDragService::GetTargetDragData(GdkAtom aFlavor)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("getting data flavor %d\n", aFlavor));
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("mLastWidget is %p and mLastContext is %p\n",
+ mTargetWidget.get(),
+ mTargetDragContext.get()));
+ // reset our target data areas
+ TargetResetData();
+ gtk_drag_get_data(mTargetWidget, mTargetDragContext, aFlavor, mTargetTime);
+
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("about to start inner iteration."));
+ PRTime entryTime = PR_Now();
+ while (!mTargetDragDataReceived && mDoingDrag) {
+ // check the number of iterations
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("doing iteration...\n"));
+ PR_Sleep(20*PR_TicksPerSecond()/1000); /* sleep for 20 ms/iteration */
+ if (PR_Now()-entryTime > NS_DND_TIMEOUT) break;
+ gtk_main_iteration();
+ }
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("finished inner iteration\n"));
+}
+
+void
+nsDragService::TargetResetData(void)
+{
+ mTargetDragDataReceived = false;
+ // make sure to free old data if we have to
+ g_free(mTargetDragData);
+ mTargetDragData = 0;
+ mTargetDragDataLen = 0;
+}
+
+GtkTargetList *
+nsDragService::GetSourceList(void)
+{
+ if (!mSourceDataItems)
+ return nullptr;
+ nsTArray<GtkTargetEntry*> targetArray;
+ GtkTargetEntry *targets;
+ GtkTargetList *targetList = 0;
+ uint32_t targetCount = 0;
+ unsigned int numDragItems = 0;
+
+ mSourceDataItems->GetLength(&numDragItems);
+
+ // Check to see if we're dragging > 1 item.
+ if (numDragItems > 1) {
+ // as the Xdnd protocol only supports a single item (or is it just
+ // gtk's implementation?), we don't advertise all flavours listed
+ // in the nsITransferable.
+
+ // the application/x-moz-internal-item-list format, which preserves
+ // all information for drags within the same mozilla instance.
+ GtkTargetEntry *listTarget =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ listTarget->target = g_strdup(gMimeListType);
+ listTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", listTarget->target));
+ targetArray.AppendElement(listTarget);
+
+ // check what flavours are supported so we can decide what other
+ // targets to advertise.
+ nsCOMPtr<nsITransferable> currItem =
+ do_QueryElementAt(mSourceDataItems, 0);
+
+ if (currItem) {
+ nsCOMPtr <nsIArray> flavorList;
+ currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+ if (flavorList) {
+ uint32_t numFlavors;
+ flavorList->GetLength( &numFlavors );
+ for (uint32_t flavorIndex = 0;
+ flavorIndex < numFlavors ;
+ ++flavorIndex ) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, flavorIndex);
+ if (currentFlavor) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ // check if text/x-moz-url is supported.
+ // If so, advertise
+ // text/uri-list.
+ if (strcmp(flavorStr, kURLMime) == 0) {
+ listTarget =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ listTarget->target = g_strdup(gTextUriListType);
+ listTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n",
+ listTarget->target));
+ targetArray.AppendElement(listTarget);
+ }
+ }
+ } // foreach flavor in item
+ } // if valid flavor list
+ } // if item is a transferable
+ } else if (numDragItems == 1) {
+ nsCOMPtr<nsITransferable> currItem =
+ do_QueryElementAt(mSourceDataItems, 0);
+ if (currItem) {
+ nsCOMPtr <nsIArray> flavorList;
+ currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+ if (flavorList) {
+ uint32_t numFlavors;
+ flavorList->GetLength( &numFlavors );
+ for (uint32_t flavorIndex = 0;
+ flavorIndex < numFlavors ;
+ ++flavorIndex ) {
+ nsCOMPtr<nsISupportsCString> currentFlavor;
+ currentFlavor = do_QueryElementAt(flavorList, flavorIndex);
+ if (currentFlavor) {
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+ GtkTargetEntry *target =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ target->target = g_strdup(flavorStr);
+ target->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("adding target %s\n", target->target));
+ targetArray.AppendElement(target);
+
+ // If there is a file, add the text/uri-list type.
+ if (strcmp(flavorStr, kFileMime) == 0) {
+ GtkTargetEntry *urilistTarget =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ urilistTarget->target = g_strdup(gTextUriListType);
+ urilistTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n",
+ urilistTarget->target));
+ targetArray.AppendElement(urilistTarget);
+ }
+ // Check to see if this is text/unicode.
+ // If it is, add text/plain
+ // since we automatically support text/plain
+ // if we support text/unicode.
+ else if (strcmp(flavorStr, kUnicodeMime) == 0) {
+ GtkTargetEntry *plainUTF8Target =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ plainUTF8Target->target = g_strdup(gTextPlainUTF8Type);
+ plainUTF8Target->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n",
+ plainUTF8Target->target));
+ targetArray.AppendElement(plainUTF8Target);
+
+ GtkTargetEntry *plainTarget =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ plainTarget->target = g_strdup(kTextMime);
+ plainTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n",
+ plainTarget->target));
+ targetArray.AppendElement(plainTarget);
+ }
+ // Check to see if this is the x-moz-url type.
+ // If it is, add _NETSCAPE_URL
+ // this is a type used by everybody.
+ else if (strcmp(flavorStr, kURLMime) == 0) {
+ GtkTargetEntry *urlTarget =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry));
+ urlTarget->target = g_strdup(gMozUrlType);
+ urlTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n",
+ urlTarget->target));
+ targetArray.AppendElement(urlTarget);
+ }
+ }
+ } // foreach flavor in item
+ } // if valid flavor list
+ } // if item is a transferable
+ } // if it is a single item drag
+
+ // get all the elements that we created.
+ targetCount = targetArray.Length();
+ if (targetCount) {
+ // allocate space to create the list of valid targets
+ targets =
+ (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry) * targetCount);
+ uint32_t targetIndex;
+ for ( targetIndex = 0; targetIndex < targetCount; ++targetIndex) {
+ GtkTargetEntry *disEntry = targetArray.ElementAt(targetIndex);
+ // this is a string reference but it will be freed later.
+ targets[targetIndex].target = disEntry->target;
+ targets[targetIndex].flags = disEntry->flags;
+ targets[targetIndex].info = 0;
+ }
+ targetList = gtk_target_list_new(targets, targetCount);
+ // clean up the target list
+ for (uint32_t cleanIndex = 0; cleanIndex < targetCount; ++cleanIndex) {
+ GtkTargetEntry *thisTarget = targetArray.ElementAt(cleanIndex);
+ g_free(thisTarget->target);
+ g_free(thisTarget);
+ }
+ g_free(targets);
+ }
+ return targetList;
+}
+
+void
+nsDragService::SourceEndDragSession(GdkDragContext *aContext,
+ gint aResult)
+{
+ // this just releases the list of data items that we provide
+ mSourceDataItems = nullptr;
+
+ if (!mDoingDrag || mScheduledTask == eDragTaskSourceEnd)
+ // EndDragSession() was already called on drop
+ // or SourceEndDragSession on drag-failed
+ return;
+
+ if (mEndDragPoint.x < 0) {
+ // We don't have a drag end point, so guess
+ gint x, y;
+ GdkDisplay* display = gdk_display_get_default();
+ if (display) {
+ gint scale = nsScreenGtk::GetGtkMonitorScaleFactor();
+ gdk_display_get_pointer(display, nullptr, &x, &y, nullptr);
+ SetDragEndPoint(LayoutDeviceIntPoint(x * scale, y * scale));
+ }
+ }
+
+ // Either the drag was aborted or the drop occurred outside the app.
+ // The dropEffect of mDataTransfer is not updated for motion outside the
+ // app, but is needed for the dragend event, so set it now.
+
+ uint32_t dropEffect;
+
+ if (aResult == MOZ_GTK_DRAG_RESULT_SUCCESS) {
+
+ // With GTK+ versions 2.10.x and prior the drag may have been
+ // cancelled (but no drag-failed signal would have been sent).
+ // aContext->dest_window will be non-nullptr only if the drop was
+ // sent.
+ GdkDragAction action =
+ gdk_drag_context_get_dest_window(aContext) ?
+ gdk_drag_context_get_actions(aContext) : (GdkDragAction)0;
+
+ // Only one bit of action should be set, but, just in case someone
+ // does something funny, erring away from MOVE, and not recording
+ // unusual action combinations as NONE.
+ if (!action)
+ dropEffect = DRAGDROP_ACTION_NONE;
+ else if (action & GDK_ACTION_COPY)
+ dropEffect = DRAGDROP_ACTION_COPY;
+ else if (action & GDK_ACTION_LINK)
+ dropEffect = DRAGDROP_ACTION_LINK;
+ else if (action & GDK_ACTION_MOVE)
+ dropEffect = DRAGDROP_ACTION_MOVE;
+ else
+ dropEffect = DRAGDROP_ACTION_COPY;
+
+ } else {
+
+ dropEffect = DRAGDROP_ACTION_NONE;
+
+ if (aResult != MOZ_GTK_DRAG_RESULT_NO_TARGET) {
+ mUserCancelled = true;
+ }
+ }
+
+ if (mDataTransfer) {
+ mDataTransfer->SetDropEffectInt(dropEffect);
+ }
+
+ // Schedule the appropriate drag end dom events.
+ Schedule(eDragTaskSourceEnd, nullptr, nullptr, LayoutDeviceIntPoint(), 0);
+}
+
+static void
+CreateUriList(nsIArray *items, gchar **text, gint *length)
+{
+ uint32_t i, count;
+ GString *uriList = g_string_new(nullptr);
+
+ items->GetLength(&count);
+ for (i = 0; i < count; i++) {
+ nsCOMPtr<nsITransferable> item;
+ item = do_QueryElementAt(items, i);
+
+ if (item) {
+ uint32_t tmpDataLen = 0;
+ void *tmpData = nullptr;
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsISupports> data;
+ rv = item->GetTransferData(kURLMime,
+ getter_AddRefs(data),
+ &tmpDataLen);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsPrimitiveHelpers::CreateDataFromPrimitive(kURLMime,
+ data,
+ &tmpData,
+ tmpDataLen);
+ char* plainTextData = nullptr;
+ char16_t* castedUnicode = reinterpret_cast<char16_t*>
+ (tmpData);
+ uint32_t plainTextLen = 0;
+ UTF16ToNewUTF8(castedUnicode,
+ tmpDataLen / 2,
+ &plainTextData,
+ &plainTextLen);
+ if (plainTextData) {
+ uint32_t j;
+
+ // text/x-moz-url is of form url + "\n" + title.
+ // We just want the url.
+ for (j = 0; j < plainTextLen; j++)
+ if (plainTextData[j] == '\n' ||
+ plainTextData[j] == '\r') {
+ plainTextData[j] = '\0';
+ break;
+ }
+ g_string_append(uriList, plainTextData);
+ g_string_append(uriList, "\r\n");
+ // this wasn't allocated with glib
+ free(plainTextData);
+ }
+ if (tmpData) {
+ // this wasn't allocated with glib
+ free(tmpData);
+ }
+ } else {
+ // There is no uri available. If there is a file available,
+ // create a uri from the file.
+ nsCOMPtr<nsISupports> data;
+ rv = item->GetTransferData(kFileMime,
+ getter_AddRefs(data),
+ &tmpDataLen);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> file = do_QueryInterface(data);
+ if (!file) {
+ // Sometimes the file is wrapped in a
+ // nsISupportsInterfacePointer. See bug 1310193 for
+ // removing this distinction.
+ nsCOMPtr<nsISupportsInterfacePointer> ptr =
+ do_QueryInterface(data);
+ if (ptr) {
+ ptr->GetData(getter_AddRefs(data));
+ file = do_QueryInterface(data);
+ }
+ }
+
+ if (file) {
+ nsCOMPtr<nsIURI> fileURI;
+ NS_NewFileURI(getter_AddRefs(fileURI), file);
+ if (fileURI) {
+ nsAutoCString uristring;
+ fileURI->GetSpec(uristring);
+ g_string_append(uriList, uristring.get());
+ g_string_append(uriList, "\r\n");
+ }
+ }
+ }
+ }
+ }
+ }
+ *text = uriList->str;
+ *length = uriList->len + 1;
+ g_string_free(uriList, FALSE); // don't free the data
+}
+
+
+void
+nsDragService::SourceDataGet(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ GtkSelectionData *aSelectionData,
+ guint32 aTime)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::SourceDataGet"));
+ GdkAtom target = gtk_selection_data_get_target(aSelectionData);
+ nsXPIDLCString mimeFlavor;
+ gchar *typeName = 0;
+ typeName = gdk_atom_name(target);
+ if (!typeName) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("failed to get atom name.\n"));
+ return;
+ }
+
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Type is %s\n", typeName));
+ // make a copy since |nsXPIDLCString| won't use |g_free|...
+ mimeFlavor.Adopt(strdup(typeName));
+ g_free(typeName);
+ // check to make sure that we have data items to return.
+ if (!mSourceDataItems) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Failed to get our data items\n"));
+ return;
+ }
+
+ nsCOMPtr<nsITransferable> item;
+ item = do_QueryElementAt(mSourceDataItems, 0);
+ if (item) {
+ // if someone was asking for text/plain, lookup unicode instead so
+ // we can convert it.
+ bool needToDoConversionToPlainText = false;
+ const char* actualFlavor = mimeFlavor;
+ if (strcmp(mimeFlavor, kTextMime) == 0 ||
+ strcmp(mimeFlavor, gTextPlainUTF8Type) == 0) {
+ actualFlavor = kUnicodeMime;
+ needToDoConversionToPlainText = true;
+ }
+ // if someone was asking for _NETSCAPE_URL we need to convert to
+ // plain text but we also need to look for x-moz-url
+ else if (strcmp(mimeFlavor, gMozUrlType) == 0) {
+ actualFlavor = kURLMime;
+ needToDoConversionToPlainText = true;
+ }
+ // if someone was asking for text/uri-list we need to convert to
+ // plain text.
+ else if (strcmp(mimeFlavor, gTextUriListType) == 0) {
+ actualFlavor = gTextUriListType;
+ needToDoConversionToPlainText = true;
+ }
+ else
+ actualFlavor = mimeFlavor;
+
+ uint32_t tmpDataLen = 0;
+ void *tmpData = nullptr;
+ nsresult rv;
+ nsCOMPtr<nsISupports> data;
+ rv = item->GetTransferData(actualFlavor,
+ getter_AddRefs(data),
+ &tmpDataLen);
+ if (NS_SUCCEEDED(rv)) {
+ nsPrimitiveHelpers::CreateDataFromPrimitive (actualFlavor, data,
+ &tmpData, tmpDataLen);
+ // if required, do the extra work to convert unicode to plain
+ // text and replace the output values with the plain text.
+ if (needToDoConversionToPlainText) {
+ char* plainTextData = nullptr;
+ char16_t* castedUnicode = reinterpret_cast<char16_t*>
+ (tmpData);
+ uint32_t plainTextLen = 0;
+ UTF16ToNewUTF8(castedUnicode,
+ tmpDataLen / 2,
+ &plainTextData,
+ &plainTextLen);
+ if (tmpData) {
+ // this was not allocated using glib
+ free(tmpData);
+ tmpData = plainTextData;
+ tmpDataLen = plainTextLen;
+ }
+ }
+ if (tmpData) {
+ // this copies the data
+ gtk_selection_data_set(aSelectionData, target,
+ 8,
+ (guchar *)tmpData, tmpDataLen);
+ // this wasn't allocated with glib
+ free(tmpData);
+ }
+ } else {
+ if (strcmp(mimeFlavor, gTextUriListType) == 0) {
+ // fall back for text/uri-list
+ gchar *uriList;
+ gint length;
+ CreateUriList(mSourceDataItems, &uriList, &length);
+ gtk_selection_data_set(aSelectionData, target,
+ 8, (guchar *)uriList, length);
+ g_free(uriList);
+ return;
+ }
+ }
+ }
+}
+
+void nsDragService::SetDragIcon(GdkDragContext* aContext)
+{
+ if (!mHasImage && !mSelection)
+ return;
+
+ LayoutDeviceIntRect dragRect;
+ nsPresContext* pc;
+ RefPtr<SourceSurface> surface;
+ DrawDrag(mSourceNode, mSourceRegion, mScreenPosition,
+ &dragRect, &surface, &pc);
+ if (!pc)
+ return;
+
+ LayoutDeviceIntPoint screenPoint =
+ ConvertToUnscaledDevPixels(pc, mScreenPosition);
+ int32_t offsetX = screenPoint.x - dragRect.x;
+ int32_t offsetY = screenPoint.y - dragRect.y;
+
+ // If a popup is set as the drag image, use its widget. Otherwise, use
+ // the surface that DrawDrag created.
+ //
+ // XXX: Disable drag popups on GTK 3.19.4 and above: see bug 1264454.
+ // Fix this once a new GTK version ships that does not destroy our
+ // widget in gtk_drag_set_icon_widget.
+ if (mDragPopup && gtk_check_version(3, 19, 4)) {
+ GtkWidget* gtkWidget = nullptr;
+ nsIFrame* frame = mDragPopup->GetPrimaryFrame();
+ if (frame) {
+ // DrawDrag ensured that this is a popup frame.
+ nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget();
+ if (widget) {
+ gtkWidget = (GtkWidget *)widget->GetNativeData(NS_NATIVE_SHELLWIDGET);
+ if (gtkWidget) {
+ OpenDragPopup();
+ gtk_drag_set_icon_widget(aContext, gtkWidget, offsetX, offsetY);
+ }
+ }
+ }
+ }
+ else if (surface) {
+ if (!SetAlphaPixmap(surface, aContext, offsetX, offsetY, dragRect)) {
+ GdkPixbuf* dragPixbuf =
+ nsImageToPixbuf::SourceSurfaceToPixbuf(surface, dragRect.width, dragRect.height);
+ if (dragPixbuf) {
+ gtk_drag_set_icon_pixbuf(aContext, dragPixbuf, offsetX, offsetY);
+ g_object_unref(dragPixbuf);
+ }
+ }
+ }
+}
+
+static void
+invisibleSourceDragBegin(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gpointer aData)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragBegin"));
+ nsDragService *dragService = (nsDragService *)aData;
+
+ dragService->SetDragIcon(aContext);
+}
+
+static void
+invisibleSourceDragDataGet(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ GtkSelectionData *aSelectionData,
+ guint aInfo,
+ guint32 aTime,
+ gpointer aData)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragDataGet"));
+ nsDragService *dragService = (nsDragService *)aData;
+ dragService->SourceDataGet(aWidget, aContext,
+ aSelectionData, aTime);
+}
+
+static gboolean
+invisibleSourceDragFailed(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gint aResult,
+ gpointer aData)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragFailed %i", aResult));
+ nsDragService *dragService = (nsDragService *)aData;
+ // End the drag session now (rather than waiting for the drag-end signal)
+ // so that operations performed on dropEffect == none can start immediately
+ // rather than waiting for the drag-failed animation to finish.
+ dragService->SourceEndDragSession(aContext, aResult);
+
+ // We should return TRUE to disable the drag-failed animation iff the
+ // source performed an operation when dropEffect was none, but the handler
+ // of the dragend DOM event doesn't provide this information.
+ return FALSE;
+}
+
+static void
+invisibleSourceDragEnd(GtkWidget *aWidget,
+ GdkDragContext *aContext,
+ gpointer aData)
+{
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragEnd"));
+ nsDragService *dragService = (nsDragService *)aData;
+
+ // The drag has ended. Release the hostages!
+ dragService->SourceEndDragSession(aContext, MOZ_GTK_DRAG_RESULT_SUCCESS);
+}
+
+// The following methods handle responding to GTK drag signals and
+// tracking state between these signals.
+//
+// In general, GTK does not expect us to run the event loop while handling its
+// drag signals, however our drag event handlers may run the
+// event loop, most often to fetch information about the drag data.
+//
+// GTK, for example, uses the return value from drag-motion signals to
+// determine whether drag-leave signals should be sent. If an event loop is
+// run during drag-motion the XdndLeave message can get processed but when GTK
+// receives the message it does not yet know that it needs to send the
+// drag-leave signal to our widget.
+//
+// After a drag-drop signal, we need to reply with gtk_drag_finish().
+// However, gtk_drag_finish should happen after the drag-drop signal handler
+// returns so that when the Motif drag protocol is used, the
+// XmTRANSFER_SUCCESS during gtk_drag_finish is sent after the XmDROP_START
+// reply sent on return from the drag-drop signal handler.
+//
+// Similarly drag-end for a successful drag and drag-failed are not good
+// times to run a nested event loop as gtk_drag_drop_finished() and
+// gtk_drag_source_info_destroy() don't gtk_drag_clear_source_info() or remove
+// drop_timeout until after at least the first of these signals is sent.
+// Processing other events (e.g. a slow GDK_DROP_FINISHED reply, or the drop
+// timeout) could cause gtk_drag_drop_finished to be called again with the
+// same GtkDragSourceInfo, which won't like being destroyed twice.
+//
+// Therefore we reply to the signals immediately and schedule a task to
+// dispatch the Gecko events, which may run the event loop.
+//
+// Action in response to drag-leave signals is also delayed until the event
+// loop runs again so that we find out whether a drag-drop signal follows.
+//
+// A single task is scheduled to manage responses to all three GTK signals.
+// If further signals are received while the task is scheduled, the scheduled
+// response is updated, sometimes effectively compressing successive signals.
+//
+// No Gecko drag events are dispatched (during nested event loops) while other
+// Gecko drag events are in flight. This helps event handlers that may not
+// expect nested events, while accessing an event's dataTransfer for example.
+
+gboolean
+nsDragService::ScheduleMotionEvent(nsWindow *aWindow,
+ GdkDragContext *aDragContext,
+ LayoutDeviceIntPoint aWindowPoint, guint aTime)
+{
+ if (mScheduledTask == eDragTaskMotion) {
+ // The drag source has sent another motion message before we've
+ // replied to the previous. That shouldn't happen with Xdnd. The
+ // spec for Motif drags is less clear, but we'll just update the
+ // scheduled task with the new position reply only to the most
+ // recent message.
+ NS_WARNING("Drag Motion message received before previous reply was sent");
+ }
+
+ // Returning TRUE means we'll reply with a status message, unless we first
+ // get a leave.
+ return Schedule(eDragTaskMotion, aWindow, aDragContext,
+ aWindowPoint, aTime);
+}
+
+void
+nsDragService::ScheduleLeaveEvent()
+{
+ // We don't know at this stage whether a drop signal will immediately
+ // follow. If the drop signal gets sent it will happen before we return
+ // to the main loop and the scheduled leave task will be replaced.
+ if (!Schedule(eDragTaskLeave, nullptr, nullptr, LayoutDeviceIntPoint(), 0)) {
+ NS_WARNING("Drag leave after drop");
+ }
+}
+
+gboolean
+nsDragService::ScheduleDropEvent(nsWindow *aWindow,
+ GdkDragContext *aDragContext,
+ LayoutDeviceIntPoint aWindowPoint, guint aTime)
+{
+ if (!Schedule(eDragTaskDrop, aWindow,
+ aDragContext, aWindowPoint, aTime)) {
+ NS_WARNING("Additional drag drop ignored");
+ return FALSE;
+ }
+
+ SetDragEndPoint(aWindowPoint + aWindow->WidgetToScreenOffset());
+
+ // We'll reply with gtk_drag_finish().
+ return TRUE;
+}
+
+gboolean
+nsDragService::Schedule(DragTask aTask, nsWindow *aWindow,
+ GdkDragContext *aDragContext,
+ LayoutDeviceIntPoint aWindowPoint, guint aTime)
+{
+ // If there is an existing leave or motion task scheduled, then that
+ // will be replaced. When the new task is run, it will dispatch
+ // any necessary leave or motion events.
+
+ // If aTask is eDragTaskSourceEnd, then it will replace even a scheduled
+ // drop event (which could happen if the drop event has not been processed
+ // within the allowed time). Otherwise, if we haven't yet run a scheduled
+ // drop or end task, just say that we are not ready to receive another
+ // drop.
+ if (mScheduledTask == eDragTaskSourceEnd ||
+ (mScheduledTask == eDragTaskDrop && aTask != eDragTaskSourceEnd))
+ return FALSE;
+
+ mScheduledTask = aTask;
+ mPendingWindow = aWindow;
+ mPendingDragContext = aDragContext;
+ mPendingWindowPoint = aWindowPoint;
+ mPendingTime = aTime;
+
+ if (!mTaskSource) {
+ // High priority is used here because the native events involved have
+ // already waited at default priority. Perhaps a lower than default
+ // priority could be used for motion tasks because there is a chance
+ // that a leave or drop is waiting, but managing different priorities
+ // may not be worth the effort. Motion tasks shouldn't queue up as
+ // they should be throttled based on replies.
+ mTaskSource = g_idle_add_full(G_PRIORITY_HIGH, TaskDispatchCallback,
+ this, nullptr);
+ }
+ return TRUE;
+}
+
+gboolean
+nsDragService::TaskDispatchCallback(gpointer data)
+{
+ RefPtr<nsDragService> dragService = static_cast<nsDragService*>(data);
+ return dragService->RunScheduledTask();
+}
+
+gboolean
+nsDragService::RunScheduledTask()
+{
+ if (mTargetWindow && mTargetWindow != mPendingWindow) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService: dispatch drag leave (%p)\n",
+ mTargetWindow.get()));
+ mTargetWindow->DispatchDragEvent(eDragExit, mTargetWindowPoint, 0);
+
+ if (!mSourceNode) {
+ // The drag that was initiated in a different app. End the drag
+ // session, since we're done with it for now (until the user drags
+ // back into this app).
+ EndDragSession(false);
+ }
+ }
+
+ // It is possible that the pending state has been updated during dispatch
+ // of the leave event. That's fine.
+
+ // Now we collect the pending state because, from this point on, we want
+ // to use the same state for all events dispatched. All state is updated
+ // so that when other tasks are scheduled during dispatch here, this
+ // task is considered to have already been run.
+ bool positionHasChanged =
+ mPendingWindow != mTargetWindow ||
+ mPendingWindowPoint != mTargetWindowPoint;
+ DragTask task = mScheduledTask;
+ mScheduledTask = eDragTaskNone;
+ mTargetWindow = mPendingWindow.forget();
+ mTargetWindowPoint = mPendingWindowPoint;
+
+ if (task == eDragTaskLeave || task == eDragTaskSourceEnd) {
+ if (task == eDragTaskSourceEnd) {
+ // Dispatch drag end events.
+ EndDragSession(true);
+ }
+
+ // Nothing more to do
+ // Returning false removes the task source from the event loop.
+ mTaskSource = 0;
+ return FALSE;
+ }
+
+ // This may be the start of a destination drag session.
+ StartDragSession();
+
+ // mTargetWidget may be nullptr if the window has been destroyed.
+ // (The leave event is not scheduled if a drop task is still scheduled.)
+ // We still reply appropriately to indicate that the drop will or didn't
+ // succeeed.
+ mTargetWidget = mTargetWindow->GetMozContainerWidget();
+ mTargetDragContext.steal(mPendingDragContext);
+ mTargetTime = mPendingTime;
+
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
+ // (as at 27 December 2010) indicates that a "drop" event should only be
+ // fired (at the current target element) if the current drag operation is
+ // not none. The current drag operation will only be set to a non-none
+ // value during a "dragover" event.
+ //
+ // If the user has ended the drag before any dragover events have been
+ // sent, then the spec recommends skipping the drop (because the current
+ // drag operation is none). However, here we assume that, by releasing
+ // the mouse button, the user has indicated that they want to drop, so we
+ // proceed with the drop where possible.
+ //
+ // In order to make the events appear to content in the same way as if the
+ // spec is being followed we make sure to dispatch a "dragover" event with
+ // appropriate coordinates and check canDrop before the "drop" event.
+ //
+ // When the Xdnd protocol is used for source/destination communication (as
+ // should be the case with GTK source applications) a dragover event
+ // should have already been sent during the drag-motion signal, which
+ // would have already been received because XdndDrop messages do not
+ // contain a position. However, we can't assume the same when the Motif
+ // protocol is used.
+ if (task == eDragTaskMotion || positionHasChanged) {
+ UpdateDragAction();
+ TakeDragEventDispatchedToChildProcess(); // Clear the old value.
+ DispatchMotionEvents();
+ if (task == eDragTaskMotion) {
+ if (TakeDragEventDispatchedToChildProcess()) {
+ mTargetDragContextForRemote = mTargetDragContext;
+ } else {
+ // Reply to tell the source whether we can drop and what
+ // action would be taken.
+ ReplyToDragMotion(mTargetDragContext);
+ }
+ }
+ }
+
+ if (task == eDragTaskDrop) {
+ gboolean success = DispatchDropEvent();
+
+ // Perhaps we should set the del parameter to TRUE when the drag
+ // action is move, but we don't know whether the data was successfully
+ // transferred.
+ gtk_drag_finish(mTargetDragContext, success,
+ /* del = */ FALSE, mTargetTime);
+
+ // This drag is over, so clear out our reference to the previous
+ // window.
+ mTargetWindow = nullptr;
+ // Make sure to end the drag session. If this drag started in a
+ // different app, we won't get a drag_end signal to end it from.
+ EndDragSession(true);
+ }
+
+ // We're done with the drag context.
+ mTargetWidget = nullptr;
+ mTargetDragContext = nullptr;
+
+ // If we got another drag signal while running the sheduled task, that
+ // must have happened while running a nested event loop. Leave the task
+ // source on the event loop.
+ if (mScheduledTask != eDragTaskNone)
+ return TRUE;
+
+ // We have no task scheduled.
+ // Returning false removes the task source from the event loop.
+ mTaskSource = 0;
+ return FALSE;
+}
+
+// This will update the drag action based on the information in the
+// drag context. Gtk gets this from a combination of the key settings
+// and what the source is offering.
+
+void
+nsDragService::UpdateDragAction()
+{
+ // This doesn't look right. dragSession.dragAction is used by
+ // nsContentUtils::SetDataTransferInEvent() to set the initial
+ // dataTransfer.dropEffect, so GdkDragContext::suggested_action would be
+ // more appropriate. GdkDragContext::actions should be used to set
+ // dataTransfer.effectAllowed, which doesn't currently happen with
+ // external sources.
+
+ // default is to do nothing
+ int action = nsIDragService::DRAGDROP_ACTION_NONE;
+ GdkDragAction gdkAction = gdk_drag_context_get_actions(mTargetDragContext);
+
+ // set the default just in case nothing matches below
+ if (gdkAction & GDK_ACTION_DEFAULT)
+ action = nsIDragService::DRAGDROP_ACTION_MOVE;
+
+ // first check to see if move is set
+ if (gdkAction & GDK_ACTION_MOVE)
+ action = nsIDragService::DRAGDROP_ACTION_MOVE;
+
+ // then fall to the others
+ else if (gdkAction & GDK_ACTION_LINK)
+ action = nsIDragService::DRAGDROP_ACTION_LINK;
+
+ // copy is ctrl
+ else if (gdkAction & GDK_ACTION_COPY)
+ action = nsIDragService::DRAGDROP_ACTION_COPY;
+
+ // update the drag information
+ SetDragAction(action);
+}
+
+NS_IMETHODIMP
+nsDragService::UpdateDragEffect()
+{
+ if (mTargetDragContextForRemote) {
+ ReplyToDragMotion(mTargetDragContextForRemote);
+ mTargetDragContextForRemote = nullptr;
+ }
+ return NS_OK;
+}
+
+void
+nsDragService::DispatchMotionEvents()
+{
+ mCanDrop = false;
+
+ FireDragEventAtSource(eDrag);
+
+ mTargetWindow->DispatchDragEvent(eDragOver, mTargetWindowPoint,
+ mTargetTime);
+}
+
+// Returns true if the drop was successful
+gboolean
+nsDragService::DispatchDropEvent()
+{
+ // We need to check IsDestroyed here because the nsRefPtr
+ // only protects this from being deleted, it does NOT protect
+ // against nsView::~nsView() calling Destroy() on it, bug 378273.
+ if (mTargetWindow->IsDestroyed())
+ return FALSE;
+
+ EventMessage msg = mCanDrop ? eDrop : eDragExit;
+
+ mTargetWindow->DispatchDragEvent(msg, mTargetWindowPoint, mTargetTime);
+
+ return mCanDrop;
+}