forked from mirrors/gecko-dev
Bug 1786251 Add support for drop file portal; r=stransky
Differential Revision: https://phabricator.services.mozilla.com/D198137
This commit is contained in:
parent
f72d46c1e7
commit
d9d7ace23b
3 changed files with 157 additions and 47 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue