Bug 396370 - Gtk (XDnd) Image/File drag and drop support r=karlt
authorTom Schuster <evilpies@gmail.com>
Thu, 04 Oct 2018 10:31:45 +0000
changeset 439555 f49d04b37bb27fbe3cbbe497c29a6d64a3b5ed47
parent 439554 7790aa7225e2426134e2414b0be0bd79c7479fe9
child 439556 fc4e7aa8f23ec6048fea85e69703deebe2b7477c
push id34778
push usernbeleuzu@mozilla.com
push dateThu, 04 Oct 2018 15:22:02 +0000
treeherdermozilla-central@01634947caab [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskarlt
bugs396370
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 396370 - Gtk (XDnd) Image/File drag and drop support r=karlt Based on a patch from Marco Pesenti Gritti (11 years ago) Depends on D7407 Differential Revision: https://phabricator.services.mozilla.com/D7408
widget/gtk/mozgtk/mozgtk.c
widget/gtk/nsDragService.cpp
widget/gtk/nsDragService.h
--- a/widget/gtk/mozgtk/mozgtk.c
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -20,16 +20,17 @@ STUB(gdk_display_get_pointer)
 STUB(gdk_display_get_window_at_pointer)
 STUB(gdk_display_manager_get)
 STUB(gdk_display_manager_set_default_display)
 STUB(gdk_display_open)
 STUB(gdk_display_sync)
 STUB(gdk_display_warp_pointer)
 STUB(gdk_drag_context_get_actions)
 STUB(gdk_drag_context_get_dest_window)
+STUB(gdk_drag_context_get_source_window)
 STUB(gdk_drag_context_list_targets)
 STUB(gdk_drag_status)
 STUB(gdk_error_trap_pop)
 STUB(gdk_error_trap_push)
 STUB(gdk_event_copy)
 STUB(gdk_event_free)
 STUB(gdk_event_get_axis)
 STUB(gdk_event_get_time)
@@ -49,16 +50,17 @@ STUB(gdk_keymap_have_bidi_layouts)
 STUB(gdk_keymap_translate_keyboard_state)
 STUB(gdk_keyval_name)
 STUB(gdk_keyval_to_unicode)
 STUB(gdk_pango_context_get)
 STUB(gdk_pointer_grab)
 STUB(gdk_pointer_ungrab)
 STUB(gdk_property_change)
 STUB(gdk_property_get)
+STUB(gdk_property_delete)
 STUB(gdk_screen_get_default)
 STUB(gdk_screen_get_display)
 STUB(gdk_screen_get_font_options)
 STUB(gdk_screen_get_height)
 STUB(gdk_screen_get_height_mm)
 STUB(gdk_screen_get_n_monitors)
 STUB(gdk_screen_get_monitor_at_window)
 STUB(gdk_screen_get_monitor_geometry)
--- a/widget/gtk/nsDragService.cpp
+++ b/widget/gtk/nsDragService.cpp
@@ -4,16 +4,17 @@
  * 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"
@@ -44,16 +45,18 @@
 #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 {
@@ -68,16 +71,17 @@ static LazyLogModule sDragLm("nsDragServ
 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,
@@ -1406,16 +1410,27 @@ nsDragService::GetSourceList(void)
                              (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);
+                        }
                     }
                 } // foreach flavor in item
             } // if valid flavor list
         } // if item is a transferable
     } // if it is a single item drag
 
     // get all the elements that we created.
     targetCount = targetArray.Length();
@@ -1445,16 +1460,20 @@ nsDragService::GetSourceList(void)
 
 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;
@@ -1650,26 +1669,126 @@ nsDragService::SourceDataGet(GtkWidget  
             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;
         }
-        else
+        // 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,
+                                  sizeof(nsIFile*));
+
+            nsCOMPtr<nsISupportsString> filenamePrimitive =
+                do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
+            if (!filenamePrimitive)
+                return;
+
+            nsAutoString leafName;
+            file->GetLeafName(leafName);
+            filenamePrimitive->SetData(leafName);
+
+            item->SetTransferData(kFilePromiseDestFilename, filenamePrimitive,
+                                  leafName.Length() * sizeof(PRUnichar));
+
+            // Request a different type in GetTransferData.
+            actualFlavor = kFilePromiseMime;
+        } else {
             actualFlavor = mimeFlavor.get();
+        }
 
         uint32_t tmpDataLen = 0;
         void    *tmpData = nullptr;
         nsresult rv;
         nsCOMPtr<nsISupports> data;
         rv = item->GetTransferData(actualFlavor,
                                    getter_AddRefs(data),
                                    &tmpDataLen);
+
+        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)) {
             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*>
@@ -1704,16 +1823,66 @@ nsDragService::SourceDataGet(GtkWidget  
                                        8, (guchar *)uriList, length);
                 g_free(uriList);
                 return;
             }
         }
     }
 }
 
+void
+nsDragService::SourceBeginDrag(GdkDragContext *aContext)
+{
+    nsCOMPtr<nsITransferable> transferable =
+        do_QueryElementAt(mSourceDataItems, 0);
+    if (!transferable)
+        return;
+
+    nsCOMPtr<nsIArray> flavorList;
+    nsresult rv = transferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+    NS_ENSURE_SUCCESS(rv,);
+
+    uint32_t length;
+    flavorList->GetLength(&length);
+
+    for (uint32_t i = 0; i < length; ++i) {
+        nsCOMPtr<nsISupportsCString> currentFlavor =
+            do_QueryElementAt(flavorList, i);
+        if (!currentFlavor)
+            return;
+
+        nsCString flavorStr;
+        currentFlavor->ToString(getter_Copies(flavorStr));
+        if (flavorStr.EqualsLiteral(kFilePromiseDestFilename)) {
+            nsCOMPtr<nsISupports> data;
+            uint32_t dataSize = 0;
+            transferable->GetTransferData(kFilePromiseDestFilename,
+                                          getter_AddRefs(data), &dataSize);
+            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;
@@ -1763,16 +1932,17 @@ void nsDragService::SetDragIcon(GdkDragC
 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,
--- a/widget/gtk/nsDragService.h
+++ b/widget/gtk/nsDragService.h
@@ -121,16 +121,18 @@ public:
     // invisible widget.
     void           SourceEndDragSession(GdkDragContext *aContext,
                                         gint            aResult);
     void           SourceDataGet(GtkWidget        *widget,
                                  GdkDragContext   *context,
                                  GtkSelectionData *selection_data,
                                  guint32           aTime);
 
+    void SourceBeginDrag(GdkDragContext *aContext);
+
     // set the drag icon during drag-begin
     void SetDragIcon(GdkDragContext* aContext);
 
 protected:
     virtual ~nsDragService();
 
 private: