diff --git a/widget/gtk/mozgtk/mozgtk.c b/widget/gtk/mozgtk/mozgtk.c index 09511d935b0e..8690c00b3a96 100644 --- a/widget/gtk/mozgtk/mozgtk.c +++ b/widget/gtk/mozgtk/mozgtk.c @@ -25,6 +25,7 @@ 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) @@ -54,6 +55,7 @@ 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) diff --git a/widget/gtk/nsDragService.cpp b/widget/gtk/nsDragService.cpp index a134845004c5..0320a54cb9ab 100644 --- a/widget/gtk/nsDragService.cpp +++ b/widget/gtk/nsDragService.cpp @@ -9,6 +9,7 @@ #include "nsIObserverService.h" #include "nsWidgetsCID.h" #include "nsWindow.h" +#include "nsSystemInfo.h" #include "nsIServiceManager.h" #include "nsXPCOM.h" #include "nsISupportsPrimitives.h" @@ -49,6 +50,8 @@ 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 @@ -73,6 +76,7 @@ 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, @@ -1411,6 +1415,17 @@ nsDragService::GetSourceList(void) 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 @@ -1450,6 +1465,10 @@ nsDragService::SourceEndDragSession(GdkDragContext *aContext, // 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 @@ -1655,8 +1674,98 @@ nsDragService::SourceDataGet(GtkWidget *aWidget, 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 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 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 directory; + file->GetParent(getter_AddRefs(directory)); + + item->SetTransferData(kFilePromiseDirectoryMime, directory, + sizeof(nsIFile*)); + + nsCOMPtr 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; @@ -1665,6 +1774,16 @@ nsDragService::SourceDataGet(GtkWidget *aWidget, 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); @@ -1709,6 +1828,56 @@ nsDragService::SourceDataGet(GtkWidget *aWidget, } } +void +nsDragService::SourceBeginDrag(GdkDragContext *aContext) +{ + nsCOMPtr transferable = + do_QueryElementAt(mSourceDataItems, 0); + if (!transferable) + return; + + nsCOMPtr 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 currentFlavor = + do_QueryElementAt(flavorList, i); + if (!currentFlavor) + return; + + nsCString flavorStr; + currentFlavor->ToString(getter_Copies(flavorStr)); + if (flavorStr.EqualsLiteral(kFilePromiseDestFilename)) { + nsCOMPtr data; + uint32_t dataSize = 0; + transferable->GetTransferData(kFilePromiseDestFilename, + getter_AddRefs(data), &dataSize); + nsCOMPtr 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) @@ -1768,6 +1937,7 @@ invisibleSourceDragBegin(GtkWidget *aWidget, MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragBegin")); nsDragService *dragService = (nsDragService *)aData; + dragService->SourceBeginDrag(aContext); dragService->SetDragIcon(aContext); } diff --git a/widget/gtk/nsDragService.h b/widget/gtk/nsDragService.h index c8090916a930..4ff2d05d8f0f 100644 --- a/widget/gtk/nsDragService.h +++ b/widget/gtk/nsDragService.h @@ -126,6 +126,8 @@ public: GtkSelectionData *selection_data, guint32 aTime); + void SourceBeginDrag(GdkDragContext *aContext); + // set the drag icon during drag-begin void SetDragIcon(GdkDragContext* aContext);