mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-07 09:54:42 +00:00
Bug 1882079 - Display real path when choosing download directory over portal r=settings-reviewers,Gijs,emilio
Use the new API addition to Document portal allowing clients to get real path to the exported document. This allows to still use the same path as provided by the document portal, but display the path as exists on the host side. Differential Revision: https://phabricator.services.mozilla.com/D202717
This commit is contained in:
parent
fb2d496b92
commit
d2084644e7
@ -3606,46 +3606,73 @@ var gMainPane = {
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (firefoxLocalizedName) {
|
||||
let folderDisplayName, leafName;
|
||||
// Either/both of these can throw, so check for failures in both cases
|
||||
// so we don't just break display of the download pref:
|
||||
try {
|
||||
folderDisplayName = file.displayName;
|
||||
} catch (ex) {
|
||||
/* ignored */
|
||||
}
|
||||
try {
|
||||
leafName = file.leafName;
|
||||
} catch (ex) {
|
||||
/* ignored */
|
||||
}
|
||||
|
||||
// If we found a localized name that's different from the leaf name,
|
||||
// use that:
|
||||
if (folderDisplayName && folderDisplayName != leafName) {
|
||||
return { file, folderDisplayName };
|
||||
}
|
||||
if (file) {
|
||||
let displayName = file.path;
|
||||
|
||||
// Otherwise, check if we've got a localized name ourselves.
|
||||
if (firefoxLocalizedName) {
|
||||
// You can't move the system download or desktop dir on macOS,
|
||||
// so if those are in use just display them. On other platforms
|
||||
// only do so if the folder matches the localized name.
|
||||
if (
|
||||
AppConstants.platform == "macosx" ||
|
||||
leafName == firefoxLocalizedName
|
||||
) {
|
||||
return { file, folderDisplayName: firefoxLocalizedName };
|
||||
// Attempt to translate path to the path as exists on the host
|
||||
// in case the provided path comes from the document portal
|
||||
if (AppConstants.platform == "linux") {
|
||||
try {
|
||||
displayName = await file.hostPath();
|
||||
} catch (error) {
|
||||
/* ignored */
|
||||
}
|
||||
|
||||
if (displayName) {
|
||||
if (displayName == downloadsDir.path) {
|
||||
firefoxLocalizedName = await document.l10n.formatValues([
|
||||
{ id: "downloads-folder-name" },
|
||||
]);
|
||||
} else if (displayName == desktopDir.path) {
|
||||
firefoxLocalizedName = await document.l10n.formatValues([
|
||||
{ id: "desktop-folder-name" },
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we get here, attempts to use a "pretty" name failed. Just display
|
||||
// the full path:
|
||||
if (file) {
|
||||
|
||||
if (firefoxLocalizedName) {
|
||||
let folderDisplayName, leafName;
|
||||
// Either/both of these can throw, so check for failures in both cases
|
||||
// so we don't just break display of the download pref:
|
||||
try {
|
||||
folderDisplayName = file.displayName;
|
||||
} catch (ex) {
|
||||
/* ignored */
|
||||
}
|
||||
try {
|
||||
leafName = file.leafName;
|
||||
} catch (ex) {
|
||||
/* ignored */
|
||||
}
|
||||
|
||||
// If we found a localized name that's different from the leaf name,
|
||||
// use that:
|
||||
if (folderDisplayName && folderDisplayName != leafName) {
|
||||
return { file, folderDisplayName };
|
||||
}
|
||||
|
||||
// Otherwise, check if we've got a localized name ourselves.
|
||||
if (firefoxLocalizedName) {
|
||||
// You can't move the system download or desktop dir on macOS,
|
||||
// so if those are in use just display them. On other platforms
|
||||
// only do so if the folder matches the localized name.
|
||||
if (
|
||||
AppConstants.platform == "macosx" ||
|
||||
leafName == firefoxLocalizedName
|
||||
) {
|
||||
return { file, folderDisplayName: firefoxLocalizedName };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, attempts to use a "pretty" name failed. Just display
|
||||
// the full path:
|
||||
// Force the left-to-right direction when displaying a custom path.
|
||||
return { file, folderDisplayName: `\u2066${file.path}\u2069` };
|
||||
return { file, folderDisplayName: `\u2066${displayName}\u2069` };
|
||||
}
|
||||
|
||||
// Don't even have a file - fall back to desktop directory for the
|
||||
// use of the icon, and an empty label:
|
||||
file = desktopDir;
|
||||
|
@ -164,6 +164,11 @@ FileDescriptorFile::SetNativeLeafName(const nsACString& aLeafName) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
FileDescriptorFile::HostPath(JSContext* aCx, dom::Promise** aPromise) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
nsresult FileDescriptorFile::InitWithPath(const nsAString& aPath) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ else:
|
||||
UNIFIED_SOURCES += [
|
||||
"nsLocalFileUnix.cpp",
|
||||
]
|
||||
CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
|
||||
|
||||
XPIDL_MODULE = "xpcom_io"
|
||||
|
||||
|
@ -128,6 +128,17 @@ interface nsIFile : nsISupports
|
||||
*/
|
||||
readonly attribute AString displayName;
|
||||
|
||||
/**
|
||||
* Linux/Flatpak specific
|
||||
* Returns path as exists on the host. Translates path provided by the document
|
||||
* portal to the path it represents on the host.
|
||||
* @returns {Promise<nsCString, nsresult>} that resolves with translated path
|
||||
* if applicable or path as it is. Rejects when Firefox runs as Flatpak and we
|
||||
* failed to translate the path.
|
||||
*/
|
||||
[implicit_jscontext]
|
||||
Promise hostPath();
|
||||
|
||||
/**
|
||||
* copyTo[Native]
|
||||
*
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
#include "mozilla/FilePreferences.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "prtime.h"
|
||||
|
||||
#include <sys/select.h>
|
||||
@ -51,6 +52,11 @@
|
||||
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
# include "nsIGIOService.h"
|
||||
# ifdef MOZ_ENABLE_DBUS
|
||||
# include "mozilla/widget/AsyncDBus.h"
|
||||
# include "mozilla/WidgetUtilsGtk.h"
|
||||
# include <map>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WIDGET_COCOA
|
||||
@ -117,6 +123,21 @@ using namespace mozilla;
|
||||
return NS_ERROR_FILE_ACCESS_DENIED; \
|
||||
} while (0)
|
||||
|
||||
#if defined(MOZ_ENABLE_DBUS) && defined(MOZ_WIDGET_GTK)
|
||||
// Prefix for files exported through document portal when we are
|
||||
// in a sandboxed environment (Flatpak).
|
||||
static const nsCString& GetDocumentStorePath() {
|
||||
static const nsDependentCString sDocumentStorePath = [] {
|
||||
nsCString storePath = nsPrintfCString("/run/user/%d/doc/", getuid());
|
||||
// Intentionally put into a ToNewCString copy, rather than just making a
|
||||
// static nsCString to avoid leakchecking errors, since we really want to
|
||||
// leak this string.
|
||||
return nsDependentCString(ToNewCString(storePath), storePath.Length());
|
||||
}();
|
||||
return sDocumentStorePath;
|
||||
}
|
||||
#endif
|
||||
|
||||
static PRTime TimespecToMillis(const struct timespec& aTimeSpec) {
|
||||
return PRTime(aTimeSpec.tv_sec) * PR_MSEC_PER_SEC +
|
||||
PRTime(aTimeSpec.tv_nsec) / PR_NSEC_PER_MSEC;
|
||||
@ -223,7 +244,7 @@ nsDirEnumeratorUnix::GetNextEntry() {
|
||||
|
||||
// keep going past "." and ".."
|
||||
} while (mEntry->d_name[0] == '.' &&
|
||||
(mEntry->d_name[1] == '\0' || // .\0
|
||||
(mEntry->d_name[1] == '\0' || // .\0
|
||||
(mEntry->d_name[1] == '.' && mEntry->d_name[2] == '\0'))); // ..\0
|
||||
return NS_OK;
|
||||
}
|
||||
@ -673,6 +694,146 @@ nsLocalFile::GetDisplayName(nsAString& aLeafName) {
|
||||
return GetLeafName(aLeafName);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsLocalFile::HostPath(JSContext* aCx, dom::Promise** aPromise) {
|
||||
MOZ_ASSERT(aCx);
|
||||
MOZ_ASSERT(aPromise);
|
||||
|
||||
nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
|
||||
if (NS_WARN_IF(!globalObject)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
ErrorResult result;
|
||||
RefPtr<dom::Promise> retPromise = dom::Promise::Create(globalObject, result);
|
||||
if (NS_WARN_IF(result.Failed())) {
|
||||
return result.StealNSResult();
|
||||
}
|
||||
|
||||
#if defined(MOZ_ENABLE_DBUS) && defined(MOZ_WIDGET_GTK)
|
||||
if (!widget::IsRunningUnderFlatpak() ||
|
||||
!StringBeginsWith(mPath, GetDocumentStorePath())) {
|
||||
retPromise->MaybeResolve(mPath);
|
||||
retPromise.forget(aPromise);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCString docId = [this] {
|
||||
auto subPath = Substring(mPath, GetDocumentStorePath().Length());
|
||||
if (auto idx = subPath.Find("/"); idx > 0) {
|
||||
subPath.Truncate(idx);
|
||||
}
|
||||
return nsCString(subPath);
|
||||
}();
|
||||
|
||||
const char kServiceName[] = "org.freedesktop.portal.Documents";
|
||||
const char kDBusPath[] = "/org/freedesktop/portal/documents";
|
||||
const char kInterfaceName[] = "org.freedesktop.portal.Documents";
|
||||
|
||||
widget::CreateDBusProxyForBus(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE,
|
||||
/* aInterfaceInfo = */ nullptr, kServiceName,
|
||||
kDBusPath, kInterfaceName)
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[this, self = RefPtr(this), docId,
|
||||
retPromise](RefPtr<GDBusProxy>&& aProxy) {
|
||||
RefPtr<GVariant> version = dont_AddRef(
|
||||
g_dbus_proxy_get_cached_property(aProxy, "version"));
|
||||
if (!version ||
|
||||
!g_variant_is_of_type(version, G_VARIANT_TYPE_UINT32)) {
|
||||
g_printerr(
|
||||
"nsIFile: failed to get host path for %s\n: Invalid value.",
|
||||
mPath.get());
|
||||
retPromise->MaybeReject(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_variant_get_uint32(version) < 5) {
|
||||
g_printerr(
|
||||
"nsIFile: failed to get host path for %s\n: Document "
|
||||
"portal in version 5 is required.",
|
||||
mPath.get());
|
||||
retPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
|
||||
return;
|
||||
}
|
||||
|
||||
GVariantBuilder builder;
|
||||
g_variant_builder_init(&builder, G_VARIANT_TYPE("(as)"));
|
||||
g_variant_builder_open(&builder, G_VARIANT_TYPE("as"));
|
||||
g_variant_builder_add(&builder, "s", docId.get());
|
||||
g_variant_builder_close(&builder);
|
||||
|
||||
RefPtr<GVariant> args = dont_AddRef(
|
||||
g_variant_ref_sink(g_variant_builder_end(&builder)));
|
||||
|
||||
if (!args) {
|
||||
g_printerr(
|
||||
"nsIFile: failed to get host path for %s\n: "
|
||||
"Invalid value.",
|
||||
mPath.get());
|
||||
retPromise->MaybeReject(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
widget::DBusProxyCall(aProxy, "GetHostPaths", args,
|
||||
G_DBUS_CALL_FLAGS_NONE, -1,
|
||||
/* cancellable */ nullptr)
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[this, self = RefPtr(this), docId,
|
||||
retPromise](RefPtr<GVariant>&& aResult) {
|
||||
RefPtr<GVariant> result = dont_AddRef(
|
||||
g_variant_get_child_value(aResult.get(), 0));
|
||||
if (!g_variant_is_of_type(result,
|
||||
G_VARIANT_TYPE("a{say}"))) {
|
||||
g_printerr(
|
||||
"nsIFile: failed to get host path for %s\n: "
|
||||
"Invalid value.",
|
||||
mPath.get());
|
||||
retPromise->MaybeReject(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
const gchar* key = nullptr;
|
||||
const gchar* path = nullptr;
|
||||
GVariantIter* iter = g_variant_iter_new(result);
|
||||
|
||||
while (
|
||||
g_variant_iter_loop(iter, "{&s^&ay}", &key, &path)) {
|
||||
if (g_strcmp0(key, docId.get()) == 0) {
|
||||
retPromise->MaybeResolve(nsDependentCString(path));
|
||||
g_variant_iter_free(iter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
g_variant_iter_free(iter);
|
||||
g_printerr(
|
||||
"nsIFile: failed to get host path for %s\n: "
|
||||
"Invalid value.",
|
||||
mPath.get());
|
||||
retPromise->MaybeReject(NS_ERROR_FAILURE);
|
||||
},
|
||||
[this, self = RefPtr(this),
|
||||
retPromise](GUniquePtr<GError>&& aError) {
|
||||
g_printerr(
|
||||
"nsIFile: failed to get host path for %s\n: %s.",
|
||||
mPath.get(), aError->message);
|
||||
retPromise->MaybeReject(NS_ERROR_FAILURE);
|
||||
});
|
||||
},
|
||||
[this, self = RefPtr(this), retPromise](GUniquePtr<GError>&& aError) {
|
||||
g_printerr("nsIFile: failed to get host path for %s\n: %s.",
|
||||
mPath.get(), aError->message);
|
||||
retPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
|
||||
});
|
||||
#else
|
||||
retPromise->MaybeResolve(mPath);
|
||||
#endif
|
||||
retPromise.forget(aPromise);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCString nsLocalFile::NativePath() { return mPath; }
|
||||
|
||||
nsresult nsIFile::GetNativePath(nsACString& aResult) {
|
||||
|
@ -3521,6 +3521,11 @@ nsLocalFile::SetNativeLeafName(const nsACString& aLeafName) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsLocalFile::HostPath(JSContext* aCx, dom::Promise** aPromise) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
nsString nsLocalFile::NativePath() { return mWorkingPath; }
|
||||
|
||||
nsCString nsIFile::HumanReadablePath() {
|
||||
|
Loading…
Reference in New Issue
Block a user