Bug 1786251 Add support for drop file portal; r=stransky

Differential Revision: https://phabricator.services.mozilla.com/D198137
This commit is contained in:
Jan Horak 2024-02-15 12:52:12 +00:00
parent f72d46c1e7
commit d9d7ace23b
3 changed files with 157 additions and 47 deletions

View file

@ -24,6 +24,7 @@ struct GFreeDeleter {
void operator()(GError* aPtr) const { g_error_free(aPtr); }
void operator()(void* aPtr) const { g_free(aPtr); }
void operator()(GtkPaperSize* aPtr) const { gtk_paper_size_free(aPtr); }
void operator()(gchar** aPtr) const { g_strfreev(aPtr); }
};
template <typename T>

View file

@ -136,6 +136,8 @@ static const char gTextUriListType[] = "text/uri-list";
static const char gTextPlainUTF8Type[] = "text/plain;charset=utf-8";
static const char gXdndDirectSaveType[] = "XdndDirectSave0";
static const char gTabDropType[] = "application/x-moz-tabbrowser-tab";
static const char gPortalFile[] = "application/vnd.portal.files";
static const char gPortalFileTransfer[] = "application/vnd.portal.filetransfer";
// See https://docs.gtk.org/gtk3/enum.DragResult.html
static const char kGtkDragResults[][100]{
@ -197,6 +199,7 @@ nsDragService::nsDragService()
// set up our logging module
mCanDrop = false;
mTargetDragDataReceived = false;
mTargetDragUris = nullptr;
mTargetDragData = 0;
mTargetDragDataLen = 0;
mTempFileTimerID = 0;
@ -636,24 +639,6 @@ static void UTF8ToNewUTF16(const char* aUTF8, uint32_t 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,
@ -709,6 +694,7 @@ nsDragService::GetNumDropItems(uint32_t* aNumItems) {
}
mSourceDataItems->GetLength(aNumItems);
} else {
// text/uri-list
GdkAtom gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
if (!gdkFlavor) {
*aNumItems = 0;
@ -719,9 +705,28 @@ nsDragService::GetNumDropItems(uint32_t* aNumItems) {
GetDragFlavors(dragFlavors);
GetTargetDragData(gdkFlavor, dragFlavors);
if (mTargetDragData) {
const char* data = reinterpret_cast<char*>(mTargetDragData);
*aNumItems = CountTextUriListItems(data, mTargetDragDataLen);
// application/vnd.portal.files
if (!mTargetDragUris) {
gdkFlavor = gdk_atom_intern(gPortalFile, FALSE);
if (!gdkFlavor) {
*aNumItems = 0;
return NS_OK;
}
GetTargetDragData(gdkFlavor, dragFlavors);
}
// application/vnd.portal.filetransfer
if (!mTargetDragUris) {
gdkFlavor = gdk_atom_intern(gPortalFileTransfer, FALSE);
if (!gdkFlavor) {
*aNumItems = 0;
return NS_OK;
}
GetTargetDragData(gdkFlavor, dragFlavors);
}
if (mTargetDragUris) {
*aNumItems = g_strv_length(mTargetDragUris.get());
} else
*aNumItems = 1;
}
@ -741,6 +746,45 @@ void nsDragService::GetDragFlavors(nsTArray<nsCString>& aFlavors) {
}
}
static nsresult GetFileFromUri(const nsCString& aUri,
nsCOMPtr<nsIFile>& aFile) {
nsresult rv;
nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
nsCOMPtr<nsIURI> fileURI;
rv = ioService->NewURI(aUri, nullptr, nullptr, getter_AddRefs(fileURI));
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv);
if (NS_SUCCEEDED(rv)) {
rv = fileURL->GetFile(getter_AddRefs(aFile));
if (NS_SUCCEEDED(rv)) {
return NS_OK;
}
}
}
return rv;
}
nsresult GetReachableFileFromUriList(char** aFileUriList, uint32_t aItemIndex,
nsCOMPtr<nsIFile>& aFile) {
if (!aFileUriList || !(g_strv_length(aFileUriList) > aItemIndex) ||
!aFileUriList[aItemIndex]) {
return NS_ERROR_INVALID_ARG;
}
nsresult rv;
rv = GetFileFromUri(nsDependentCString(aFileUriList[aItemIndex]), aFile);
if (NS_SUCCEEDED(rv)) {
bool fileExists = false;
aFile->Exists(&fileExists);
if (fileExists) {
LOGDRAG(" good, file %s exists\n", aFileUriList[aItemIndex]);
return NS_OK;
}
}
LOGDRAG(" uri %s not reachable/not found\n", aFileUriList[aItemIndex]);
aFile = nullptr;
return NS_ERROR_FILE_NOT_FOUND;
}
// Spins event loop, called from JS.
// Can lead to another round of drag_motion events.
NS_IMETHODIMP
@ -820,9 +864,40 @@ nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) {
LOGDRAGSERVICE(" we're getting data %s (gdk flavor %p)\n", flavorStr.get(),
gdkFlavor);
bool dataFound = false;
nsCOMPtr<nsIFile> file;
if (gdkFlavor) {
GetTargetDragData(gdkFlavor, dragFlavors);
GetReachableFileFromUriList(mTargetDragUris.get(), aItemIndex, file);
}
// application/vnd.portal.files
if (!file || !mTargetDragUris) {
LOGDRAGSERVICE(" file not found, proceed with %s flavor\n", gPortalFile);
gdkFlavor = gdk_atom_intern(gPortalFile, FALSE);
if (gdkFlavor) {
GetTargetDragData(gdkFlavor, dragFlavors);
GetReachableFileFromUriList(mTargetDragUris.get(), aItemIndex, file);
}
}
// application/vnd.portal.filetransfer
if (!file || !mTargetDragUris) {
LOGDRAGSERVICE(" file not found, proceed with %s flavor\n",
gPortalFileTransfer);
gdkFlavor = gdk_atom_intern(gPortalFileTransfer, FALSE);
if (gdkFlavor) {
GetTargetDragData(gdkFlavor, dragFlavors);
GetReachableFileFromUriList(mTargetDragUris.get(), aItemIndex, file);
}
}
if (file) {
LOGDRAGSERVICE(" from drag uris set as file %s - flavor: %s",
mTargetDragUris.get()[aItemIndex], flavorStr.get());
aTransferable->SetTransferData(flavorStr.get(), file);
return NS_OK;
}
if (mTargetDragData) {
LOGDRAGSERVICE(" dataFound = true\n");
dataFound = true;
@ -844,28 +919,19 @@ nsDragService::GetData(nsITransferable* aTransferable, uint32_t 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));
nsCOMPtr<nsIFile> file;
rv = GetFileFromUri(NS_ConvertUTF16toUTF8(convertedText), file);
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv);
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIFile> file;
rv = fileURL->GetFile(getter_AddRefs(file));
if (NS_SUCCEEDED(rv)) {
// The common wrapping code at the end of
// this function assumes the data is text
// and calls text-specific operations.
// Make a secret hideout here for nsIFile
// objects and return early.
LOGDRAGSERVICE(" set as file %s",
NS_ConvertUTF16toUTF8(convertedText).get());
aTransferable->SetTransferData(flavorStr.get(), file);
g_free(convertedText);
return NS_OK;
}
}
// The common wrapping code at the end of
// this function assumes the data is text
// and calls text-specific operations.
// Make a secret hideout here for nsIFile
// objects and return early.
LOGDRAGSERVICE(" set as file %s",
NS_ConvertUTF16toUTF8(convertedText).get());
aTransferable->SetTransferData(flavorStr.get(), file);
g_free(convertedText);
return NS_OK;
}
g_free(convertedText);
}
@ -1062,7 +1128,15 @@ nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
(strcmp(aDataFlavor, kURLMime) == 0)) {
*_retval = true;
}
// check for file portal
// If we're asked for kURLMime/kFileMime we can convert gPortalFile
// or gPortalFileTransfer to it.
else if ((strcmp(name.get(), gPortalFile) == 0 ||
strcmp(name.get(), gPortalFileTransfer) == 0) &&
(strcmp(aDataFlavor, kURLMime) == 0 ||
strcmp(aDataFlavor, kFileMime) == 0)) {
*_retval = true;
}
if (*_retval) {
LOGDRAGSERVICE(" supported, with converting %s => %s", name.get(),
aDataFlavor);
@ -1136,6 +1210,7 @@ void nsDragService::ReplyToDragMotion(GdkDragContext* aDragContext,
void nsDragService::EnsureCachedDataValidForContext(
GdkDragContext* aDragContext) {
if (mCachedDragContext != (uintptr_t)aDragContext) {
mCachedUris.Clear();
mCachedData.Clear();
mCachedDragContext = (uintptr_t)aDragContext;
}
@ -1152,15 +1227,35 @@ void nsDragService::TargetDataReceived(GtkWidget* aWidget,
EnsureCachedDataValidForContext(aContext);
mTargetDragDataReceived = true;
gint len = gtk_selection_data_get_length(aSelectionData);
const guchar* data = gtk_selection_data_get_data(aSelectionData);
GdkAtom target = gtk_selection_data_get_target(aSelectionData);
GUniquePtr<gchar> name(gdk_atom_name(target));
nsDependentCString flavor(name.get());
LOGDRAGSERVICE(" got data, MIME %s", flavor.get());
if (gtk_targets_include_uri(&target, 1)) {
GUniquePtr<gchar*> uris(gtk_selection_data_get_uris(aSelectionData));
#ifdef MOZ_LOGGING
if (MOZ_LOG_TEST(gWidgetDragLog, mozilla::LogLevel::Debug)) {
gchar** uri = uris.get();
while (uri && *uri) {
LOGDRAGSERVICE(" got uri %s, MIME %s", *uri, flavor.get());
uri++;
}
}
#endif
uris.swap(mTargetDragUris);
if (mTargetDragUris) {
mCachedUris.InsertOrUpdate(
flavor, GUniquePtr<gchar*>(g_strdupv(mTargetDragUris.get())));
} else {
LOGDRAGSERVICE("Failed to get uri list\n");
mCachedUris.InsertOrUpdate(flavor, GUniquePtr<gchar*>(nullptr));
}
return;
}
const guchar* data = gtk_selection_data_get_data(aSelectionData);
gint len = gtk_selection_data_get_length(aSelectionData);
if (len > 0 && data) {
mTargetDragDataLen = len;
mTargetDragData = g_malloc(mTargetDragDataLen);
@ -1180,6 +1275,7 @@ void nsDragService::TargetDataReceived(GtkWidget* aWidget,
mTargetDragDataLen);
mCachedData.InsertOrUpdate(flavor, nsTArray<uint8_t>());
}
LOGDRAGSERVICE(" got data, MIME %s", flavor.get());
}
bool nsDragService::IsTargetContextList(void) {
@ -1240,6 +1336,14 @@ void nsDragService::GetTargetDragData(GdkAtom aFlavor,
// Especially with multiple items the same data is requested
// very often.
EnsureCachedDataValidForContext(mTargetDragContext);
if (auto cached = mCachedUris.Lookup(flavor)) {
LOGDRAGSERVICE(" using cached uri list for %s", flavor.get());
mTargetDragUris = GUniquePtr<gchar*>(g_strdupv(cached->get()));
mTargetDragDataReceived = true;
LOGDRAGSERVICE(" %s found in cache", flavor.get());
return;
}
if (auto cached = mCachedData.Lookup(flavor)) {
mTargetDragDataLen = cached->Length();
LOGDRAGSERVICE(" using cached data for %s, length is %d", flavor.get(),
@ -1274,7 +1378,7 @@ void nsDragService::GetTargetDragData(GdkAtom aFlavor,
}
#ifdef MOZ_LOGGING
if (mTargetDragDataLen && mTargetDragData) {
if (mTargetDragUris || (mTargetDragDataLen && mTargetDragData)) {
LOGDRAGSERVICE(" %s got from system", flavor.get());
} else {
LOGDRAGSERVICE(" %s failed to get from system", flavor.get());
@ -1285,6 +1389,7 @@ void nsDragService::GetTargetDragData(GdkAtom aFlavor,
void nsDragService::TargetResetData(void) {
mTargetDragDataReceived = false;
// make sure to free old data if we have to
mTargetDragUris = nullptr;
g_free(mTargetDragData);
mTargetDragData = 0;
mTargetDragDataLen = 0;

View file

@ -13,6 +13,7 @@
#include "nsIObserver.h"
#include <gtk/gtk.h>
#include "nsITimer.h"
#include "GUniquePtr.h"
class nsICookieJarSettings;
class nsWindow;
@ -168,6 +169,8 @@ class nsDragService final : public nsBaseDragService, public nsIObserver {
// any D&D operation.
uintptr_t mCachedDragContext;
nsTHashMap<nsCStringHashKey, mozilla::GUniquePtr<gchar*>> mCachedUris;
guint mPendingTime;
// mTargetWindow and mTargetWindowPoint record the position of the last
@ -199,6 +202,7 @@ class nsDragService final : public nsBaseDragService, public nsIObserver {
// last data received and its length
void* mTargetDragData;
uint32_t mTargetDragDataLen;
mozilla::GUniquePtr<gchar*> mTargetDragUris;
// is the current target drag context contain a list?
bool IsTargetContextList(void);
// this will get the native data from the last target given a