зеркало из https://github.com/mozilla/gecko-dev.git
2043 строки
74 KiB
C++
2043 строки
74 KiB
C++
/* -*- 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 "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 "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/PresShell.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"
|
|
#ifdef MOZ_WAYLAND
|
|
# include "nsClipboardWayland.h"
|
|
#endif
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::gfx;
|
|
|
|
#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 };
|
|
|
|
static LazyLogModule sDragLm("nsDragService");
|
|
|
|
// 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 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)
|
|
#ifdef MOZ_WAYLAND
|
|
,
|
|
mPendingWaylandDragContext(nullptr),
|
|
mTargetWaylandDragContext(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
|
|
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)
|
|
|
|
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")) {
|
|
MOZ_LOG(sDragLm, LogLevel::Debug,
|
|
("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
|
|
|
|
// 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 = 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.
|
|
//
|
|
// 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(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(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(
|
|
nsINode* aDOMNode, nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp,
|
|
nsIArray* aArrayTransferables, 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, aPrincipal, aCsp,
|
|
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));
|
|
|
|
#ifdef MOZ_WIDGET_GTK
|
|
// 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);
|
|
|
|
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
|
|
// 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 = ScreenHelperGTK::GetGTKMonitorScaleFactor();
|
|
sCairoSurfaceSetDeviceScalePtr(surf, scale, scale);
|
|
}
|
|
|
|
gtk_drag_set_icon_surface(aContext, surf);
|
|
cairo_surface_destroy(surf);
|
|
return true;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsDragService::StartDragSession() {
|
|
MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::StartDragSession"));
|
|
return nsBaseDragService::StartDragSession();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
|
|
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;
|
|
#ifdef MOZ_WAYLAND
|
|
mTargetWaylandDragContextForRemote = nullptr;
|
|
#endif
|
|
|
|
return nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
#ifdef MOZ_WAYLAND
|
|
// TODO: Wayland implementation of text/uri-list.
|
|
if (!mTargetDragContext) {
|
|
*aNumItems = 1;
|
|
return NS_OK;
|
|
}
|
|
#endif
|
|
|
|
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).
|
|
nsTArray<nsCString> flavors;
|
|
nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// 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 (uint32_t i = 0; i < flavors.Length(); ++i) {
|
|
nsCString& flavorStr = flavors[i];
|
|
MOZ_LOG(sDragLm, LogLevel::Debug, ("flavor is %s\n", flavorStr.get()));
|
|
// get the item with the right index
|
|
nsCOMPtr<nsITransferable> item =
|
|
do_QueryElementAt(mSourceDataItems, aItemIndex);
|
|
if (!item) continue;
|
|
|
|
nsCOMPtr<nsISupports> data;
|
|
MOZ_LOG(sDragLm, LogLevel::Debug,
|
|
("trying to get transfer data for %s\n", flavorStr.get()));
|
|
rv = item->GetTransferData(flavorStr.get(), getter_AddRefs(data));
|
|
if (NS_FAILED(rv)) {
|
|
MOZ_LOG(sDragLm, LogLevel::Debug, ("failed.\n"));
|
|
continue;
|
|
}
|
|
MOZ_LOG(sDragLm, LogLevel::Debug, ("succeeded.\n"));
|
|
rv = aTransferable->SetTransferData(flavorStr.get(), data);
|
|
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 (uint32_t i = 0; i < flavors.Length(); ++i) {
|
|
nsCString& flavorStr = flavors[i];
|
|
GdkAtom gdkFlavor = gdk_atom_intern(flavorStr.get(), FALSE);
|
|
MOZ_LOG(sDragLm, LogLevel::Debug,
|
|
("looking for data in type %s, gdk flavor %p\n", flavorStr.get(),
|
|
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 (flavorStr.EqualsLiteral(kFileMime)) {
|
|
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.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)) {
|
|
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 (flavorStr.EqualsLiteral(kURLMime)) {
|
|
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 (!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!
|
|
MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound and converted!\n"));
|
|
break;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
nsTArray<nsCString> flavors;
|
|
currItem->FlavorsTransferableCanExport(flavors);
|
|
for (uint32_t i = 0; i < flavors.Length(); ++i) {
|
|
MOZ_LOG(sDragLm, LogLevel::Debug,
|
|
("checking %s against %s\n", flavors[i].get(), aDataFlavor));
|
|
if (flavors[i].Equals(aDataFlavor)) {
|
|
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 = nullptr;
|
|
if (mTargetDragContext) {
|
|
tmp = gdk_drag_context_list_targets(mTargetDragContext);
|
|
}
|
|
#ifdef MOZ_WAYLAND
|
|
else if (mTargetWaylandDragContext) {
|
|
tmp = mTargetWaylandDragContext->GetTargets();
|
|
}
|
|
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);
|
|
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);
|
|
}
|
|
|
|
#ifdef MOZ_WAYLAND
|
|
// mTargetWaylandDragContext->GetTargets allocates the list
|
|
// so we need to free it here.
|
|
if (!mTargetDragContext && tmp_head) {
|
|
g_list_free(tmp_head);
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
|
|
#ifdef MOZ_WAYLAND
|
|
void nsDragService::ReplyToDragMotion(nsWaylandDragContext* 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;
|
|
}
|
|
}
|
|
|
|
aDragContext->SetDragStatus(action);
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
|
|
#ifdef MOZ_WAYLAND
|
|
// TODO: We need a wayland implementation here.
|
|
if (!mTargetDragContext) return retval;
|
|
#endif
|
|
|
|
// 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 %p\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();
|
|
|
|
if (mTargetDragContext) {
|
|
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();
|
|
}
|
|
}
|
|
#ifdef MOZ_WAYLAND
|
|
else {
|
|
mTargetDragData = mTargetWaylandDragContext->GetData(gdk_atom_name(aFlavor),
|
|
&mTargetDragDataLen);
|
|
mTargetDragDataReceived = true;
|
|
}
|
|
#endif
|
|
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) {
|
|
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)) {
|
|
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);
|
|
}
|
|
}
|
|
} // 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];
|
|
|
|
GtkTargetEntry* target =
|
|
(GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
|
|
target->target = g_strdup(flavorStr.get());
|
|
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 (flavorStr.EqualsLiteral(kFileMime)) {
|
|
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 (flavorStr.EqualsLiteral(kUnicodeMime)) {
|
|
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 (flavorStr.EqualsLiteral(kURLMime)) {
|
|
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);
|
|
}
|
|
// XdndDirectSave
|
|
else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
|
|
GtkTargetEntry* directsaveTarget =
|
|
(GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
|
|
directsaveTarget->target = g_strdup(gXdndDirectSaveType);
|
|
directsaveTarget->flags = 0;
|
|
MOZ_LOG(
|
|
sDragLm, LogLevel::Debug,
|
|
("automatically adding target %s\n", directsaveTarget->target));
|
|
targetArray.AppendElement(directsaveTarget);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
|
|
// 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 = ScreenHelperGTK::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, 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);
|
|
}
|
|
|
|
AppendUTF16toUTF8(text, aURIList);
|
|
aURIList.AppendLiteral("\r\n");
|
|
continue;
|
|
}
|
|
|
|
// There is no URI available. If there is a file available, create
|
|
// a URI from the file.
|
|
rv = item->GetTransferData(kFileMime, getter_AddRefs(data));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
if (nsCOMPtr<nsIFile> file = do_QueryInterface(data)) {
|
|
nsCOMPtr<nsIURI> fileURI;
|
|
NS_NewFileURI(getter_AddRefs(fileURI), file);
|
|
if (fileURI) {
|
|
nsAutoCString spec;
|
|
fileURI->GetSpec(spec);
|
|
|
|
aURIList.Append(spec);
|
|
aURIList.AppendLiteral("\r\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
nsCString 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 |nsCString| 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;
|
|
if (mimeFlavor.EqualsLiteral(kTextMime) ||
|
|
mimeFlavor.EqualsLiteral(gTextPlainUTF8Type)) {
|
|
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 (mimeFlavor.EqualsLiteral(gMozUrlType)) {
|
|
actualFlavor = kURLMime;
|
|
needToDoConversionToPlainText = true;
|
|
}
|
|
// if someone was asking for text/uri-list we need to convert to
|
|
// plain text.
|
|
else if (mimeFlavor.EqualsLiteral(gTextUriListType)) {
|
|
actualFlavor = gTextUriListType;
|
|
needToDoConversionToPlainText = true;
|
|
}
|
|
// Someone is asking for the special Direct Save Protocol type.
|
|
else if (mimeFlavor.EqualsLiteral(gXdndDirectSaveType)) {
|
|
// Indicate failure by default.
|
|
gtk_selection_data_set(aSelectionData, target, 8, (guchar*)"E", 1);
|
|
|
|
GdkAtom property = gdk_atom_intern(gXdndDirectSaveType, FALSE);
|
|
GdkAtom type = gdk_atom_intern(kTextMime, FALSE);
|
|
|
|
guchar* data;
|
|
gint length;
|
|
if (!gdk_property_get(gdk_drag_context_get_source_window(aContext),
|
|
property, type, 0, INT32_MAX, FALSE, nullptr,
|
|
nullptr, &length, &data)) {
|
|
return;
|
|
}
|
|
|
|
// Zero-terminate the string.
|
|
data = (guchar*)g_realloc(data, length + 1);
|
|
if (!data) return;
|
|
data[length] = '\0';
|
|
|
|
gchar* hostname;
|
|
char* gfullpath =
|
|
g_filename_from_uri((const gchar*)data, &hostname, nullptr);
|
|
g_free(data);
|
|
if (!gfullpath) return;
|
|
|
|
nsCString fullpath(gfullpath);
|
|
g_free(gfullpath);
|
|
|
|
MOZ_LOG(sDragLm, LogLevel::Debug,
|
|
("XdndDirectSave filepath is %s\n", fullpath.get()));
|
|
|
|
// If there is no hostname in the URI, NULL will be stored.
|
|
// We should not accept uris with from a different host.
|
|
if (hostname) {
|
|
nsCOMPtr<nsIPropertyBag2> infoService =
|
|
do_GetService(NS_SYSTEMINFO_CONTRACTID);
|
|
if (!infoService) return;
|
|
|
|
nsAutoCString host;
|
|
if (NS_SUCCEEDED(infoService->GetPropertyAsACString(
|
|
NS_LITERAL_STRING("host"), host))) {
|
|
if (!host.Equals(hostname)) {
|
|
MOZ_LOG(sDragLm, LogLevel::Debug,
|
|
("ignored drag because of different host.\n"));
|
|
|
|
// Special error code "F" for this case.
|
|
gtk_selection_data_set(aSelectionData, target, 8, (guchar*)"F", 1);
|
|
g_free(hostname);
|
|
return;
|
|
}
|
|
}
|
|
|
|
g_free(hostname);
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> file;
|
|
if (NS_FAILED(
|
|
NS_NewNativeLocalFile(fullpath, false, getter_AddRefs(file)))) {
|
|
return;
|
|
}
|
|
|
|
// We have to split the path into a directory and filename,
|
|
// because our internal file-promise API is based on these.
|
|
|
|
nsCOMPtr<nsIFile> directory;
|
|
file->GetParent(getter_AddRefs(directory));
|
|
|
|
item->SetTransferData(kFilePromiseDirectoryMime, directory);
|
|
|
|
nsCOMPtr<nsISupportsString> filenamePrimitive =
|
|
do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
|
|
if (!filenamePrimitive) return;
|
|
|
|
nsAutoString leafName;
|
|
file->GetLeafName(leafName);
|
|
filenamePrimitive->SetData(leafName);
|
|
|
|
item->SetTransferData(kFilePromiseDestFilename, filenamePrimitive);
|
|
|
|
// Request a different type in GetTransferData.
|
|
actualFlavor = kFilePromiseMime;
|
|
} else {
|
|
actualFlavor = mimeFlavor.get();
|
|
}
|
|
nsresult rv;
|
|
nsCOMPtr<nsISupports> data;
|
|
rv = item->GetTransferData(actualFlavor, getter_AddRefs(data));
|
|
|
|
if (strcmp(actualFlavor, kFilePromiseMime) == 0) {
|
|
if (NS_SUCCEEDED(rv)) {
|
|
// Indicate success.
|
|
gtk_selection_data_set(aSelectionData, target, 8, (guchar*)"S", 1);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
void* tmpData = nullptr;
|
|
uint32_t tmpDataLen = 0;
|
|
nsPrimitiveHelpers::CreateDataFromPrimitive(
|
|
nsDependentCString(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 (mimeFlavor.EqualsLiteral(gTextUriListType)) {
|
|
// fall back for text/uri-list
|
|
nsAutoCString list;
|
|
CreateURIList(mSourceDataItems, list);
|
|
gtk_selection_data_set(aSelectionData, target, 8, (guchar*)list.get(),
|
|
list.Length());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void nsDragService::SourceBeginDrag(GdkDragContext* aContext) {
|
|
nsCOMPtr<nsITransferable> transferable =
|
|
do_QueryElementAt(mSourceDataItems, 0);
|
|
if (!transferable) return;
|
|
|
|
nsTArray<nsCString> flavors;
|
|
nsresult rv = transferable->FlavorsTransferableCanImport(flavors);
|
|
NS_ENSURE_SUCCESS(rv, );
|
|
|
|
for (uint32_t i = 0; i < flavors.Length(); ++i) {
|
|
if (flavors[i].EqualsLiteral(kFilePromiseDestFilename)) {
|
|
nsCOMPtr<nsISupports> data;
|
|
rv = transferable->GetTransferData(kFilePromiseDestFilename,
|
|
getter_AddRefs(data));
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsISupportsString> fileName = do_QueryInterface(data);
|
|
if (!fileName) {
|
|
return;
|
|
}
|
|
|
|
nsAutoString fileNameStr;
|
|
fileName->GetData(fileNameStr);
|
|
|
|
nsCString fileNameCStr;
|
|
CopyUTF16toUTF8(fileNameStr, fileNameCStr);
|
|
|
|
GdkAtom property = gdk_atom_intern(gXdndDirectSaveType, FALSE);
|
|
GdkAtom type = gdk_atom_intern(kTextMime, FALSE);
|
|
|
|
gdk_property_change(gdk_drag_context_get_source_window(aContext),
|
|
property, type, 8, GDK_PROP_MODE_REPLACE,
|
|
(const guchar*)fileNameCStr.get(),
|
|
fileNameCStr.Length());
|
|
}
|
|
}
|
|
}
|
|
|
|
void nsDragService::SetDragIcon(GdkDragContext* aContext) {
|
|
if (!mHasImage && !mSelection) return;
|
|
|
|
LayoutDeviceIntRect dragRect;
|
|
nsPresContext* pc;
|
|
RefPtr<SourceSurface> surface;
|
|
DrawDrag(mSourceNode, mRegion, 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->SourceBeginDrag(aContext);
|
|
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,
|
|
nsWaylandDragContext* aWaylandDragContext,
|
|
LayoutDeviceIntPoint aWindowPoint, guint aTime) {
|
|
if (aDragContext && 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, aWaylandDragContext,
|
|
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, nullptr,
|
|
LayoutDeviceIntPoint(), 0)) {
|
|
NS_WARNING("Drag leave after drop");
|
|
}
|
|
}
|
|
|
|
gboolean nsDragService::ScheduleDropEvent(
|
|
nsWindow* aWindow, GdkDragContext* aDragContext,
|
|
nsWaylandDragContext* aWaylandDragContext,
|
|
LayoutDeviceIntPoint aWindowPoint, guint aTime) {
|
|
if (!Schedule(eDragTaskDrop, aWindow, aDragContext, aWaylandDragContext,
|
|
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,
|
|
nsWaylandDragContext* aWaylandDragContext,
|
|
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;
|
|
#ifdef MOZ_WAYLAND
|
|
mPendingWaylandDragContext = aWaylandDragContext;
|
|
#endif
|
|
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, GetCurrentModifiers());
|
|
}
|
|
}
|
|
|
|
// 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, GetCurrentModifiers());
|
|
}
|
|
|
|
// 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);
|
|
#ifdef MOZ_WAYLAND
|
|
mTargetWaylandDragContext = mPendingWaylandDragContext.forget();
|
|
#endif
|
|
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;
|
|
#ifdef MOZ_WAYLAND
|
|
mTargetWaylandDragContextForRemote = mTargetWaylandDragContext;
|
|
#endif
|
|
} else {
|
|
// Reply to tell the source whether we can drop and what
|
|
// action would be taken.
|
|
if (mTargetDragContext) {
|
|
ReplyToDragMotion(mTargetDragContext);
|
|
}
|
|
#ifdef MOZ_WAYLAND
|
|
else if (mTargetWaylandDragContext) {
|
|
ReplyToDragMotion(mTargetWaylandDragContext);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
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.
|
|
if (mTargetDragContext) {
|
|
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, GetCurrentModifiers());
|
|
}
|
|
|
|
// We're done with the drag context.
|
|
mTargetWidget = nullptr;
|
|
mTargetDragContext = nullptr;
|
|
#ifdef MOZ_WAYLAND
|
|
mTargetWaylandDragContext = nullptr;
|
|
#endif
|
|
|
|
// 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_ACTION_DEFAULT;
|
|
if (mTargetDragContext) {
|
|
gdkAction = gdk_drag_context_get_actions(mTargetDragContext);
|
|
}
|
|
#ifdef MOZ_WAYLAND
|
|
else if (mTargetWaylandDragContext) {
|
|
// We got the selected D&D action from compositor on Wayland.
|
|
gdkAction = mTargetWaylandDragContext->GetSelectedDragAction();
|
|
}
|
|
#endif
|
|
|
|
// 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;
|
|
}
|
|
#ifdef MOZ_WAYLAND
|
|
else if (mTargetWaylandDragContextForRemote) {
|
|
ReplyToDragMotion(mTargetWaylandDragContextForRemote);
|
|
mTargetWaylandDragContextForRemote = nullptr;
|
|
}
|
|
#endif
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsDragService::DispatchMotionEvents() {
|
|
mCanDrop = false;
|
|
|
|
FireDragEventAtSource(eDrag, GetCurrentModifiers());
|
|
|
|
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;
|
|
}
|
|
|
|
/* static */
|
|
uint32_t nsDragService::GetCurrentModifiers() {
|
|
return mozilla::widget::KeymapWrapper::ComputeCurrentKeyModifiers();
|
|
}
|