Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=4 et sw=2 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 "nsSystemInfo.h"
#include "nsXPCOM.h"
#include "nsICookieJarSettings.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 "mozilla/ClearOnShutdown.h"
#include "mozilla/PresShell.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/WidgetUtilsGtk.h"
#include "GRefPtr.h"
#include "gfxXlibSurface.h"
#include "gfxContext.h"
#include "nsImageToPixbuf.h"
#include "nsPresContext.h"
#include "nsIContent.h"
#include "mozilla/dom/Document.h"
#include "nsViewManager.h"
#include "nsIFrame.h"
#include "nsGtkUtils.h"
#include "nsGtkKeyUtils.h"
#include "mozilla/gfx/2D.h"
#include "gfxPlatform.h"
#include "ScreenHelperGTK.h"
#include "nsArrayUtils.h"
#include "nsStringStream.h"
#ifdef MOZ_WAYLAND
# include "nsClipboardWayland.h"
# include "gfxPlatformGtk.h"
#endif
#include "nsDirectoryService.h"
#include "nsDirectoryServiceDefs.h"
#include "nsEscape.h"
#include "nsString.h"
using namespace mozilla;
using namespace mozilla::gfx;
// 1) The maximum time to wait for a "drag_received" arrived in microseconds.
// 2) The maximum time to wait before temporary files resulting
// from drag'n'drop events will be removed
#define NS_DND_TIMEOUT 1000000
#define NS_SYSTEMINFO_CONTRACTID "@mozilla.org/system-info;1"
// 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,
MOZ_GTK_DRAG_RESULT_USER_CANCELLED,
MOZ_GTK_DRAG_RESULT_TIMEOUT_EXPIRED,
MOZ_GTK_DRAG_RESULT_GRAB_BROKEN,
MOZ_GTK_DRAG_RESULT_ERROR
};
#ifdef MOZ_LOGGING
extern mozilla::LazyLogModule gWidgetDragLog;
# define LOGDRAGSERVICE(args) \
MOZ_LOG(gWidgetDragLog, mozilla::LogLevel::Debug, args)
#else
# define LOGDRAGSERVICE(args)
#endif
// 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 const char gXdndDirectSaveType[] = "XdndDirectSave0";
static const char gTabDropType[] = "application/x-moz-tabbrowser-tab";
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),
mScheduledTaskIsRunning(false),
mCachedDragContext()
#ifdef MOZ_WAYLAND
,
mPendingWaylandDataOffer(nullptr),
mTargetWaylandDataOffer(nullptr)
#endif
{
// 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
// Using an offscreen window works around bug 983843.
mHiddenWidget = gtk_offscreen_window_new();
// 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
LOGDRAGSERVICE(("nsDragService::nsDragService"));
// We begin with enabled drop on Wayland due to different
// Wayland D&D architecture (Bug 1730203).
mCanDrop = widget::GdkIsWaylandDisplay();
mTargetDragDataReceived = false;
mTargetDragData = 0;
mTargetDragDataLen = 0;
mTempFileTimerID = 0;
}
nsDragService::~nsDragService() {
LOGDRAGSERVICE(("nsDragService::~nsDragService"));
if (mTaskSource) g_source_remove(mTaskSource);
if (mTempFileTimerID) g_source_remove(mTempFileTimerID);
}
NS_IMPL_ISUPPORTS_INHERITED(nsDragService, nsBaseDragService, nsIObserver)
mozilla::StaticRefPtr<nsDragService> sDragServiceInstance;
/* static */
already_AddRefed<nsDragService> nsDragService::GetInstance() {
if (gfxPlatform::IsHeadless()) {
return nullptr;
}
if (!sDragServiceInstance) {
sDragServiceInstance = new nsDragService();
ClearOnShutdown(&sDragServiceInstance);
}
RefPtr<nsDragService> service = sDragServiceInstance.get();
return service.forget();
}
// nsIObserver
NS_IMETHODIMP
nsDragService::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (!nsCRT::strcmp(aTopic, "quit-application")) {
LOGDRAGSERVICE(("nsDragService::Observe(\"quit-application\")"));
if (mHiddenWidget) {
gtk_widget_destroy(mHiddenWidget);
mHiddenWidget = 0;
}
TargetResetData();
} else {
MOZ_ASSERT_UNREACHABLE("unexpected topic");
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
// Support for periodic drag events
// 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 = mozilla::widget::ScreenHelperGTK::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.
//
// recommends an interval of 350ms +/- 200ms.
sMotionEventTimerID = g_timeout_add_full(
G_PRIORITY_DEFAULT_IDLE, 350, DispatchMotionEventCopy, nullptr, nullptr);
}
static GtkWindow* GetGtkWindow(dom::Document* aDocument) {
if (!aDocument) return nullptr;
PresShell* presShell = aDocument->GetPresShell();
if (!presShell) {
return nullptr;
}
RefPtr<nsViewManager> vm = presShell->GetViewManager();
if (!vm) return nullptr;
nsCOMPtr<nsIWidget> widget = vm->GetRootWidget();
if (!widget) return nullptr;
GtkWidget* gtkWidget = static_cast<nsWindow*>(widget.get())->GetGtkWidget();
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(
nsINode* aDOMNode, nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp,
nsICookieJarSettings* aCookieJarSettings, nsIArray* aArrayTransferables,
uint32_t aActionType,
nsContentPolicyType aContentPolicyType = nsIContentPolicy::TYPE_OTHER) {
LOGDRAGSERVICE(("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, aPrincipal, aCsp, aCookieJarSettings, aArrayTransferables,
aActionType, aContentPolicyType);
}
// nsBaseDragService
nsresult nsDragService::InvokeDragSessionImpl(
nsIArray* aArrayTransferables, const Maybe<CSSIntRegion>& 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;
// 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));
// 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);
// start our drag.
GdkDragContext* context =
gtk_drag_begin(mHiddenWidget, sourceList, action, 1, &event);
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;
#ifdef cairo_image_surface_create
# error "Looks like we're including Mozilla's cairo instead of system cairo"
#endif
// 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 = mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor();
sCairoSurfaceSetDeviceScalePtr(surf, scale, scale);
}
gtk_drag_set_icon_surface(aContext, surf);
cairo_surface_destroy(surf);
return true;
}
NS_IMETHODIMP
nsDragService::StartDragSession() {
LOGDRAGSERVICE(("nsDragService::StartDragSession"));
mTempFileUrl.Truncate();
return nsBaseDragService::StartDragSession();
}
bool nsDragService::RemoveTempFiles() {
// We can not delete the temporary files immediately after the
// drag has finished, because the target application might have not
// copied the temporary file yet. The Qt toolkit does not provide a
// way to mark a drop as finished in an asynchronous way, so most
// Qt based applications do send the dnd_finished signal before they
// have actually accessed the data from the temporary file.
//
// To work also with these applications we collect all temporary
// files in mTemporaryFiles array and remove them here in the timer event.
int32_t count = mTemporaryFiles.Count();
for (int32_t pos = 0; pos < count; pos++) {
mTemporaryFiles[pos]->Remove(/* recursive = */ true);
}
mTemporaryFiles.Clear();
mTempFileTimerID = 0;
// Return false to remove the timer added by g_timeout_add_full().
return false;
}
gboolean nsDragService::TaskRemoveTempFiles(gpointer data) {
RefPtr<nsDragService> dragService = static_cast<nsDragService*>(data);
return dragService->RemoveTempFiles();
}
NS_IMETHODIMP
nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
LOGDRAGSERVICE(("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);
// start timer to remove temporary files
if (mTemporaryFiles.Count() > 0 && !mTempFileTimerID) {
LOGDRAGSERVICE((" removing temporary files"));
// |this| won't be used after nsDragService delete because the timer is
// removed in the nsDragService destructor.
mTempFileTimerID = g_timeout_add(NS_DND_TIMEOUT, TaskRemoveTempFiles, this);
mTempFileUrl.Truncate();
}
// We're done with the drag context.
mTargetDragContextForRemote = nullptr;
#ifdef MOZ_WAYLAND
mTargetWaylandDataOfferForRemote = nullptr;
#endif
mTargetWindow = nullptr;
mPendingWindow = nullptr;
mCachedDragContext = 0;
return nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
}
// nsIDragSession
NS_IMETHODIMP
nsDragService::SetCanDrop(bool aCanDrop) {
LOGDRAGSERVICE(("nsDragService::SetCanDrop %d", aCanDrop));
mCanDrop = aCanDrop;
return NS_OK;
}
NS_IMETHODIMP
nsDragService::GetCanDrop(bool* aCanDrop) {
LOGDRAGSERVICE(("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) {
LOGDRAGSERVICE(("nsDragService::GetNumDropItems"));
if (!mTargetWidget) {
LOGDRAGSERVICE(
("*** warning: GetNumDropItems \
called without a valid target widget!\n"));
*aNumItems = 0;
return NS_OK;
}
bool isList = IsTargetContextList();
if (isList) {
if (!mSourceDataItems) {
*aNumItems = 0;
return NS_OK;
}
mSourceDataItems->GetLength(aNumItems);
} else {
GdkAtom gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
if (!gdkFlavor) {
*aNumItems = 0;
return NS_OK;
}
nsTArray<nsCString> dragFlavors;
GetDragFlavors(dragFlavors);
GetTargetDragData(gdkFlavor, dragFlavors);
if (mTargetDragData) {
const char* data = reinterpret_cast<char*>(mTargetDragData);
*aNumItems = CountTextUriListItems(data, mTargetDragDataLen);
} else
*aNumItems = 1;
}
LOGDRAGSERVICE((" NumOfDropItems %d", *aNumItems));
return NS_OK;
}
void nsDragService::GetDragFlavors(nsTArray<nsCString>& aFlavors) {
for (GList* tmp = gdk_drag_context_list_targets(mTargetDragContext); tmp;
tmp = tmp->next) {
GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
gchar* name = gdk_atom_name(atom);
if (!name) {
continue;
}
aFlavors.AppendElement(nsCString(name));
}
}
NS_IMETHODIMP
nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) {
LOGDRAGSERVICE(("nsDragService::GetData(), index %d", aItemIndex));
// make sure that we have a transferable
if (!aTransferable) {
return NS_ERROR_INVALID_ARG;
}
if (!mTargetWidget) {
LOGDRAGSERVICE(
("*** failed: 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).
nsTArray<nsCString> flavors;
nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
if (NS_FAILED(rv)) {
LOGDRAGSERVICE((" failed to get flavors, quit."));
return rv;
}
// check to see if this is an internal list
bool isList = IsTargetContextList();
if (isList) {
LOGDRAGSERVICE((" Process as a list..."));
// find a matching flavor
for (uint32_t i = 0; i < flavors.Length(); ++i) {
nsCString& flavorStr = flavors[i];
LOGDRAGSERVICE((" [%d] flavor is %s\n", i, flavorStr.get()));
// get the item with the right index
nsCOMPtr<nsITransferable> item =
do_QueryElementAt(mSourceDataItems, aItemIndex);
if (!item) continue;
nsCOMPtr<nsISupports> data;
LOGDRAGSERVICE(
(" trying to get transfer data for %s\n", flavorStr.get()));
rv = item->GetTransferData(flavorStr.get(), getter_AddRefs(data));
if (NS_FAILED(rv)) {
LOGDRAGSERVICE((" failed.\n"));
continue;
}
rv = aTransferable->SetTransferData(flavorStr.get(), data);
if (NS_FAILED(rv)) {
LOGDRAGSERVICE((" fail to set transfer data into transferable!\n"));
continue;
}
LOGDRAGSERVICE((" succeeded\n"));
// ok, we got the data
return NS_OK;
}
// if we got this far, we failed
LOGDRAGSERVICE((" failed to match flavors\n"));
return NS_ERROR_FAILURE;
}
nsTArray<nsCString> dragFlavors;
GetDragFlavors(dragFlavors);
// 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 (uint32_t i = 0; i < flavors.Length(); ++i) {
nsCString& flavorStr = flavors[i];
GdkAtom gdkFlavor = gdk_atom_intern(flavorStr.get(), FALSE);
LOGDRAGSERVICE((" we're getting data %s (gdk flavor %p)\n",
flavorStr.get(), gdkFlavor));
bool dataFound = false;
if (gdkFlavor) {
GetTargetDragData(gdkFlavor, dragFlavors);
}
if (mTargetDragData) {
LOGDRAGSERVICE((" dataFound = true\n"));
dataFound = true;
} else {
LOGDRAGSERVICE((" dataFound = false, try conversions\n"));
// Dragging and dropping from the file manager would cause us
// to parse the source text as a nsIFile URL.
if (flavorStr.EqualsLiteral(kFileMime)) {
gdkFlavor = gdk_atom_intern(kTextMime, FALSE);
LOGDRAGSERVICE((" conversion %s => %s", kFileMime, kTextMime));
GetTargetDragData(gdkFlavor, dragFlavors);
if (!mTargetDragData) {
LOGDRAGSERVICE(
(" conversion %s => %s", kFileMime, gTextUriListType));
gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
GetTargetDragData(gdkFlavor, dragFlavors);
}
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.
LOGDRAGSERVICE((" set as file %s",
NS_ConvertUTF16toUTF8(convertedText).get()));
aTransferable->SetTransferData(flavorStr.get(), file);
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 (flavorStr.EqualsLiteral(kUnicodeMime)) {
LOGDRAGSERVICE(
(" conversion %s => %s", kUnicodeMime, gTextPlainUTF8Type));
gdkFlavor = gdk_atom_intern(gTextPlainUTF8Type, FALSE);
GetTargetDragData(gdkFlavor, dragFlavors);
if (mTargetDragData) {
const char* castedText = reinterpret_cast<char*>(mTargetDragData);
char16_t* convertedText = nullptr;
NS_ConvertUTF8toUTF16 ucs2string(castedText, mTargetDragDataLen);
convertedText = ToNewUnicode(ucs2string, mozilla::fallible);
if (convertedText) {
// 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 {
LOGDRAGSERVICE((" conversion %s => %s", kUnicodeMime, kTextMime));
gdkFlavor = gdk_atom_intern(kTextMime, FALSE);
GetTargetDragData(gdkFlavor, dragFlavors);
if (mTargetDragData) {
const char* castedText = reinterpret_cast<char*>(mTargetDragData);
char16_t* convertedText = nullptr;
uint32_t convertedTextLen = 0;
UTF8ToNewUTF16(castedText, mTargetDragDataLen, &convertedText,
&convertedTextLen);
if (convertedText) {
// 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 (flavorStr.EqualsLiteral(kURLMime)) {
LOGDRAGSERVICE((" conversion %s => %s", kURLMime, gTextUriListType));
gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
GetTargetDragData(gdkFlavor, dragFlavors);
if (mTargetDragData) {
const char* data = reinterpret_cast<char*>(mTargetDragData);
char16_t* convertedText = nullptr;
uint32_t convertedTextLen = 0;
GetTextUriListItem(data, mTargetDragDataLen, aItemIndex,
&convertedText, &convertedTextLen);
if (convertedText) {
// out with the old, in with the new
g_free(mTargetDragData);
mTargetDragData = convertedText;
mTargetDragDataLen = convertedTextLen * 2;
dataFound = true;
}
}
if (!dataFound) {
LOGDRAGSERVICE((" conversion %s => %s", kURLMime, gMozUrlType));
gdkFlavor = gdk_atom_intern(gMozUrlType, FALSE);
GetTargetDragData(gdkFlavor, dragFlavors);
if (mTargetDragData) {
const char* castedText = reinterpret_cast<char*>(mTargetDragData);
char16_t* convertedText = nullptr;
uint32_t convertedTextLen = 0;
UTF8ToNewUTF16(castedText, mTargetDragDataLen, &convertedText,
&convertedTextLen);
if (convertedText) {
// out with the old, in with the new
g_free(mTargetDragData);
mTargetDragData = convertedText;
mTargetDragDataLen = convertedTextLen * 2;
dataFound = true;
}
}
}
}
} // else we try one last ditch effort to find our data
if (dataFound) {
#if MOZ_LOGGING
char* name = gdk_atom_name(gdkFlavor);
if (name) {
LOGDRAGSERVICE((" actual data found %s\n", name));
g_free(name);
}
#endif
if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
flavorStr.EqualsLiteral(kJPGImageMime) ||
flavorStr.EqualsLiteral(kPNGImageMime) ||
flavorStr.EqualsLiteral(kGIFImageMime)) {
LOGDRAGSERVICE((" saving as image %s\n", flavorStr.get()));
nsCOMPtr<nsIInputStream> byteStream;
NS_NewByteInputStream(getter_AddRefs(byteStream),
Span((char*)mTargetDragData, mTargetDragDataLen),
NS_ASSIGNMENT_COPY);
aTransferable->SetTransferData(flavorStr.get(), byteStream);
continue;
}
if (!flavorStr.EqualsLiteral(kCustomTypesMime)) {
// 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.get(), genericDataWrapper);
// we found one, get out of this loop!
break;
}
}
return NS_OK;
}
NS_IMETHODIMP
nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
LOGDRAGSERVICE(("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) {
LOGDRAGSERVICE(
("*** 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) {
LOGDRAGSERVICE((" 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) {
LOGDRAGSERVICE((" quit"));
return NS_OK;
}
mSourceDataItems->GetLength(&numDragItems);
LOGDRAGSERVICE((" drag items %d", numDragItems));
for (uint32_t itemIndex = 0; itemIndex < numDragItems; ++itemIndex) {
nsCOMPtr<nsITransferable> currItem =
do_QueryElementAt(mSourceDataItems, itemIndex);
if (currItem) {
nsTArray<nsCString> flavors;
currItem->FlavorsTransferableCanExport(flavors);
for (uint32_t i = 0; i < flavors.Length(); ++i) {
LOGDRAGSERVICE(
(" checking %s against %s\n", flavors[i].get(), aDataFlavor));
if (flavors[i].Equals(aDataFlavor)) {
LOGDRAGSERVICE((" found.\n"));
*_retval = true;
}
}
}
}
return NS_OK;
}
// check the target context vs. this flavor, one at a time
GList* tmp = nullptr;
if (mTargetDragContext) {
tmp = gdk_drag_context_list_targets(mTargetDragContext);
}
#ifdef MOZ_WAYLAND
else if (mTargetWaylandDataOffer) {
tmp = mTargetWaylandDataOffer->GetDragTargets();
}
GList* tmp_head = tmp;
#endif
for (; tmp; tmp = tmp->next) {
/* Bug 331198 */
GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
gchar* name = nullptr;
name = gdk_atom_name(atom);
if (!name) {
continue;
}
if (strcmp(name, aDataFlavor) == 0) {
*_retval = true;
}
// check for automatic text/uri-list -> text/x-moz-url mapping
else if (strcmp(name, gTextUriListType) == 0 &&
(strcmp(aDataFlavor, kURLMime) == 0 ||
strcmp(aDataFlavor, kFileMime) == 0)) {
*_retval = true;
}
// check for automatic _NETSCAPE_URL -> text/x-moz-url mapping
else if (strcmp(name, gMozUrlType) == 0 &&
(strcmp(aDataFlavor, kURLMime) == 0)) {
*_retval = true;
}
// check for auto text/plain -> text/unicode mapping
else if (strcmp(name, kTextMime) == 0 &&
((strcmp(aDataFlavor, kUnicodeMime) == 0) ||
(strcmp(aDataFlavor, kFileMime) == 0))) {
*_retval = true;
}
if (*_retval) {
LOGDRAGSERVICE(
(" supported, with converting %s => %s", name, aDataFlavor));
}
g_free(name);
}
#ifdef MOZ_WAYLAND
// mTargetWaylandDataOffer->GetDragTargets allocates the list
// so we need to free it here.
if (!mTargetDragContext && tmp_head) {
g_list_free(tmp_head);
}
#endif
if (!*_retval) {
LOGDRAGSERVICE((" %s is not supported", aDataFlavor));
}
return NS_OK;
}
void nsDragService::ReplyToDragMotion(GdkDragContext* aDragContext) {
LOGDRAGSERVICE(("nsDragService::ReplyToDragMotion can drop %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;
}
}
LOGDRAGSERVICE((" gdk_drag_status() action %d", action));
gdk_drag_status(aDragContext, action, mTargetTime);
}
#ifdef MOZ_WAYLAND
void nsDragService::ReplyToDragMotion(RefPtr<DataOffer> aDragContext) {
LOGDRAGSERVICE(("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;
}
}
aDragContext->SetDragStatus(action);
}
#endif
void nsDragService::EnsureCachedDataValidForContext(
GdkDragContext* aDragContext) {
if (mCachedDragContext != (uintptr_t)aDragContext) {
mCachedData.Clear();
mCachedDragContext = (uintptr_t)aDragContext;
}
}
void nsDragService::TargetDataReceived(GtkWidget* aWidget,
GdkDragContext* aContext, gint aX,
gint aY,
GtkSelectionData* aSelectionData,
guint aInfo, guint32 aTime) {
LOGDRAGSERVICE(("nsDragService::TargetDataReceived"));
TargetResetData();
EnsureCachedDataValidForContext(aContext);
mTargetDragDataReceived = true;
gint len = gtk_selection_data_get_length(aSelectionData);
const guchar* data = gtk_selection_data_get_data(aSelectionData);
GdkAtom target = gtk_selection_data_get_target(aSelectionData);
char* name = gdk_atom_name(target);
nsCString flavor(name);
g_free(name);
LOGDRAGSERVICE((" got data, MIME %s", flavor.get()));
if (len > 0 && data) {
mTargetDragDataLen = len;
mTargetDragData = g_malloc(mTargetDragDataLen);
memcpy(mTargetDragData, data, mTargetDragDataLen);
LOGDRAGSERVICE((" got data, len = %d", mTargetDragDataLen));
nsTArray<uint8_t> copy;
if (!copy.SetLength(len, fallible)) {
return;
}
memcpy(copy.Elements(), data, len);
mCachedData.InsertOrUpdate(flavor, std::move(copy));
} else {
LOGDRAGSERVICE(("Failed to get data. selection data len was %d\n",
mTargetDragDataLen));
mCachedData.InsertOrUpdate(flavor, nsTArray<uint8_t>());
}
}
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 (mTargetDragContext &&
gtk_drag_get_source_widget(mTargetDragContext) == nullptr) {
return retval;
}
GList* tmp = nullptr;
if (mTargetDragContext) {
tmp = gdk_drag_context_list_targets(mTargetDragContext);
}
#ifdef MOZ_WAYLAND
GList* tmp_head = nullptr;
if (mTargetWaylandDataOffer) {
tmp_head = tmp = mTargetWaylandDataOffer->GetDragTargets();
}
#endif
// walk the list of context targets and see if one of them is a list
// of items.
for (; 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;
}
#ifdef MOZ_WAYLAND
// mTargetWaylandDataOffer->GetDragTargets allocates the list
// so we need to free it here.
if (mTargetWaylandDataOffer && tmp_head) {
g_list_free(tmp_head);
}
#endif
return retval;
}
void nsDragService::GetTargetDragData(GdkAtom aFlavor,
nsTArray<nsCString>& aDropFlavors) {
LOGDRAGSERVICE(
("nsDragService::GetTargetDragData '%s'\n", gdk_atom_name(aFlavor)));
// reset our target data areas
TargetResetData();
char* name = gdk_atom_name(aFlavor);
nsCString flavor(name);
g_free(name);
// Return early when requested MIME is not offered by D&D.
if (!aDropFlavors.Contains(flavor)) {
LOGDRAGSERVICE((" %s is missing", flavor.get()));
return;
}
if (mTargetDragContext) {
// We keep a copy of the requested data with the same life-time
// as mTargetDragContext.
// Especially with multiple items the same data is requested
// very often.
EnsureCachedDataValidForContext(mTargetDragContext);
if (auto cached = mCachedData.Lookup(flavor)) {
mTargetDragDataLen = cached->Length();
LOGDRAGSERVICE((" using cached data for %s, length is %d", flavor.get(),
mTargetDragDataLen));
if (mTargetDragDataLen) {
mTargetDragData = g_malloc(mTargetDragDataLen);
memcpy(mTargetDragData, cached->Elements(), mTargetDragDataLen);
}
mTargetDragDataReceived = true;
LOGDRAGSERVICE((" %s found in cache", flavor.get()));
return;
}
gtk_drag_get_data(mTargetWidget, mTargetDragContext, aFlavor, mTargetTime);
LOGDRAGSERVICE((" about to start inner iteration."));
gtk_main_iteration();
PRTime entryTime = PR_Now();
while (!mTargetDragDataReceived && mDoingDrag) {
// check the number of iterations
LOGDRAGSERVICE((" doing iteration...\n"));
PR_Sleep(PR_MillisecondsToInterval(10)); /* sleep for 10 ms/iteration */
if (PR_Now() - entryTime > NS_DND_TIMEOUT) {
LOGDRAGSERVICE((" failed to get D&D data in time!\n"));
break;
}
gtk_main_iteration();
}
}
#ifdef MOZ_WAYLAND
else if (mTargetWaylandDataOffer) {
mTargetDragData = mTargetWaylandDataOffer->GetDragData(
gdk_atom_name(aFlavor), &mTargetDragDataLen);
mTargetDragDataReceived = true;
}
#endif
#ifdef MOZ_LOGGING
if (mTargetDragDataLen && mTargetDragData) {
LOGDRAGSERVICE((" %s got from system", flavor.get()));
} else {
LOGDRAGSERVICE((" %s failed to get from system", flavor.get()));
}
#endif
}
void nsDragService::TargetResetData(void) {
mTargetDragDataReceived = false;
// make sure to free old data if we have to
g_free(mTargetDragData);
mTargetDragData = 0;
mTargetDragDataLen = 0;
}
static void TargetArrayAddTarget(nsTArray<GtkTargetEntry*>& aTargetArray,
const char* aTarget) {
GtkTargetEntry* target = (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
target->target = g_strdup(aTarget);
target->flags = 0;
aTargetArray.AppendElement(target);
LOGDRAGSERVICE(("adding target %s\n", aTarget));
}
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.
TargetArrayAddTarget(targetArray, gMimeListType);
// check what flavours are supported so we can decide what other
// targets to advertise.
nsCOMPtr<nsITransferable> currItem = do_QueryElementAt(mSourceDataItems, 0);
if (currItem) {
nsTArray<nsCString> flavors;
currItem->FlavorsTransferableCanExport(flavors);
for (uint32_t i = 0; i < flavors.Length(); ++i) {
// check if text/x-moz-url is supported.
// If so, advertise
// text/uri-list.
if (flavors[i].EqualsLiteral(kURLMime)) {
TargetArrayAddTarget(targetArray, gTextUriListType);
break;
}
}
} // if item is a transferable
} else if (numDragItems == 1) {
nsCOMPtr<nsITransferable> currItem = do_QueryElementAt(mSourceDataItems, 0);
if (currItem) {
nsTArray<nsCString> flavors;
currItem->FlavorsTransferableCanExport(flavors);
for (uint32_t i = 0; i < flavors.Length(); ++i) {
nsCString& flavorStr = flavors[i];
TargetArrayAddTarget(targetArray, flavorStr.get());
// If there is a file, add the text/uri-list type.
if (flavorStr.EqualsLiteral(kFileMime)) {
TargetArrayAddTarget(targetArray, gTextUriListType);
}
// 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 (flavorStr.EqualsLiteral(kUnicodeMime)) {
TargetArrayAddTarget(targetArray, gTextPlainUTF8Type);
TargetArrayAddTarget(targetArray, kTextMime);
}
// 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 (flavorStr.EqualsLiteral(kURLMime)) {
TargetArrayAddTarget(targetArray, gMozUrlType);
}
// check if application/x-moz-file-promise url is supported.
// If so, advertise text/uri-list.
else if (flavorStr.EqualsLiteral(kFilePromiseURLMime)) {
TargetArrayAddTarget(targetArray, gTextUriListType);
}
// XdndDirectSave
else if (widget::GdkIsX11Display() &&
flavorStr.EqualsLiteral(kFilePromiseMime)) {
TargetArrayAddTarget(targetArray, gXdndDirectSaveType);
}
// kNativeImageMime
else if (flavorStr.EqualsLiteral(kNativeImageMime)) {
TargetArrayAddTarget(targetArray, kPNGImageMime);
TargetArrayAddTarget(targetArray, kJPEGImageMime);
TargetArrayAddTarget(targetArray, kJPGImageMime);
TargetArrayAddTarget(targetArray, kGIFImageMime);
}
}
}
}
// 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);
} else {
// We need to create a dummy target list to be able initialize dnd.
targetList = gtk_target_list_new(nullptr, 0);
}
return targetList;
}
void nsDragService::SourceEndDragSession(GdkDragContext* aContext,
gint aResult) {
LOGDRAGSERVICE(("SourceEndDragSession result %d\n", aResult));
// this just releases the list of data items that we provide
mSourceDataItems = nullptr;
// Remove this property, if it exists, to satisfy the Direct Save Protocol.
GdkAtom property = gdk_atom_intern(gXdndDirectSaveType, FALSE);
gdk_property_delete(gdk_drag_context_get_source_window(aContext), property);
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 = mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor();
gdk_display_get_pointer(display, nullptr, &x, &y, nullptr);
SetDragEndPoint(LayoutDeviceIntPoint(x * scale, y * scale));
LOGDRAGSERVICE(("guess drag end point %d %d\n", 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) {
LOGDRAGSERVICE(("drop is user chancelled\n"));
mUserCancelled = true;
}
}
if (mDataTransfer) {
mDataTransfer->SetDropEffectInt(dropEffect);
}
// Schedule the appropriate drag end dom events.
Schedule(eDragTaskSourceEnd, nullptr, nullptr, nullptr,
LayoutDeviceIntPoint(), 0);
}
static void CreateURIList(nsIArray* aItems, nsACString& aURIList) {
uint32_t length = 0;
aItems->GetLength(&length);
for (uint32_t i = 0; i < length; ++i) {
nsCOMPtr<nsITransferable> item = do_QueryElementAt(aItems, i);
if (!item) {
continue;
}
nsCOMPtr<nsISupports> data;
nsresult rv = item->GetTransferData(kURLMime, getter_AddRefs(data));
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsISupportsString> string = do_QueryInterface(data);
nsAutoString text;
if (string) {
string->GetData(text);
}
// text/x-moz-url is of form url + "\n" + title.
// We just want the url.
int32_t separatorPos = text.FindChar(u'\n');
if (separatorPos >= 0) {
text.Truncate(separatorPos);