diff options
Diffstat (limited to 'widget/gtk/nsDragService.cpp')
-rw-r--r-- | widget/gtk/nsDragService.cpp | 2109 |
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; +} |