summaryrefslogtreecommitdiffstats
path: root/toolkit/system/gnome/nsGIOService.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/system/gnome/nsGIOService.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/system/gnome/nsGIOService.cpp')
-rw-r--r--toolkit/system/gnome/nsGIOService.cpp951
1 files changed, 951 insertions, 0 deletions
diff --git a/toolkit/system/gnome/nsGIOService.cpp b/toolkit/system/gnome/nsGIOService.cpp
new file mode 100644
index 0000000000..d02560b68f
--- /dev/null
+++ b/toolkit/system/gnome/nsGIOService.cpp
@@ -0,0 +1,951 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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 "nsGIOService.h"
+#include "nsString.h"
+#include "nsIURI.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+#include "nsStringEnumerator.h"
+#include "nsIMIMEInfo.h"
+#include "nsComponentManagerUtils.h"
+#include "nsArray.h"
+#include "nsPrintfCString.h"
+#include "mozilla/GRefPtr.h"
+#include "mozilla/GUniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/WidgetUtilsGtk.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/net/DNS.h"
+#include "prenv.h"
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#ifdef MOZ_ENABLE_DBUS
+# include <fcntl.h>
+# include <dlfcn.h>
+# include "mozilla/widget/AsyncDBus.h"
+# include "mozilla/WidgetUtilsGtk.h"
+#endif
+
+using namespace mozilla;
+
+class nsFlatpakHandlerApp : public nsIHandlerApp {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+ nsFlatpakHandlerApp() = default;
+
+ private:
+ virtual ~nsFlatpakHandlerApp() = default;
+};
+
+NS_IMPL_ISUPPORTS(nsFlatpakHandlerApp, nsIHandlerApp)
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::GetName(nsAString& aName) {
+ aName.AssignLiteral("System Handler");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::SetName(const nsAString& aName) {
+ // We don't implement SetName because flatpak system handler name is fixed
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::GetDetailedDescription(nsAString& aDetailedDescription) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::SetDetailedDescription(
+ const nsAString& aDetailedDescription) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::Equals(nsIHandlerApp* aHandlerApp, bool* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::LaunchWithURI(
+ nsIURI* aUri, mozilla::dom::BrowsingContext* aBrowsingContext) {
+ nsCString spec;
+ aUri->GetSpec(spec);
+ GUniquePtr<GError> error;
+
+ // The TMPDIR where files are downloaded when user choose to open them
+ // needs to be accessible from sandbox and host. The default settings
+ // TMPDIR=/tmp is accessible only to the sandbox. That can be the reason
+ // why the gtk_show_uri fails there.
+ // The workaround is to set TMPDIR environment variable in sandbox to
+ // $XDG_CACHE_HOME/tmp before executing Firefox.
+ gtk_show_uri(nullptr, spec.get(), GDK_CURRENT_TIME, getter_Transfers(error));
+ if (error) {
+ NS_WARNING(
+ nsPrintfCString("Cannot launch flatpak handler: %s", error->message)
+ .get());
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+/**
+ * Get command without any additional arguments
+ * @param aCommandWithArguments full commandline input string
+ * @param aCommand string for storing command without arguments
+ * @return NS_ERROR_FAILURE when unable to parse commandline
+ */
+static nsresult GetCommandFromCommandline(
+ nsACString const& aCommandWithArguments, nsACString& aCommand) {
+ GUniquePtr<GError> error;
+ gchar** argv = nullptr;
+ if (!g_shell_parse_argv(aCommandWithArguments.BeginReading(), nullptr, &argv,
+ getter_Transfers(error)) ||
+ !argv[0]) {
+ g_warning("Cannot parse command with arguments: %s", error->message);
+ g_strfreev(argv);
+ return NS_ERROR_FAILURE;
+ }
+ aCommand.Assign(argv[0]);
+ g_strfreev(argv);
+ return NS_OK;
+}
+
+class nsGIOMimeApp final : public nsIGIOMimeApp {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIHANDLERAPP
+ NS_DECL_NSIGIOMIMEAPP
+
+ explicit nsGIOMimeApp(already_AddRefed<GAppInfo> aApp) : mApp(aApp) {}
+
+ private:
+ ~nsGIOMimeApp() = default;
+
+ RefPtr<GAppInfo> mApp;
+};
+
+NS_IMPL_ISUPPORTS(nsGIOMimeApp, nsIGIOMimeApp, nsIHandlerApp)
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetId(nsACString& aId) {
+ aId.Assign(g_app_info_get_id(mApp));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetName(nsAString& aName) {
+ aName.Assign(NS_ConvertUTF8toUTF16(g_app_info_get_name(mApp)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::SetName(const nsAString& aName) {
+ // We don't implement SetName because we're using mGIOMimeApp instance for
+ // obtaining application name
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetCommand(nsACString& aCommand) {
+ const char* cmd = g_app_info_get_commandline(mApp);
+ if (!cmd) return NS_ERROR_FAILURE;
+ aCommand.Assign(cmd);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetExpectsURIs(int32_t* aExpects) {
+ *aExpects = g_app_info_supports_uris(mApp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetDetailedDescription(nsAString& aDetailedDescription) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::SetDetailedDescription(const nsAString& aDetailedDescription) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::Equals(nsIHandlerApp* aHandlerApp, bool* _retval) {
+ if (!aHandlerApp) return NS_ERROR_FAILURE;
+
+ // Compare with nsILocalHandlerApp instance by name
+ nsCOMPtr<nsILocalHandlerApp> localHandlerApp = do_QueryInterface(aHandlerApp);
+ if (localHandlerApp) {
+ nsAutoString theirName;
+ nsAutoString thisName;
+ GetName(thisName);
+ localHandlerApp->GetName(theirName);
+ *_retval = thisName.Equals(theirName);
+ return NS_OK;
+ }
+
+ // Compare with nsIGIOMimeApp instance by command with stripped arguments
+ nsCOMPtr<nsIGIOMimeApp> gioMimeApp = do_QueryInterface(aHandlerApp);
+ if (gioMimeApp) {
+ nsAutoCString thisCommandline, thisCommand;
+ nsresult rv = GetCommand(thisCommandline);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetCommandFromCommandline(thisCommandline, thisCommand);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString theirCommandline, theirCommand;
+ gioMimeApp->GetCommand(theirCommandline);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetCommandFromCommandline(theirCommandline, theirCommand);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *_retval = thisCommand.Equals(theirCommand);
+ return NS_OK;
+ }
+
+ // We can only compare with nsILocalHandlerApp and nsGIOMimeApp
+ *_retval = false;
+ return NS_OK;
+}
+
+static RefPtr<GAppLaunchContext> GetLaunchContext(
+ const char* aXDGToken = nullptr) {
+ RefPtr<GAppLaunchContext> context = dont_AddRef(g_app_launch_context_new());
+ // Unset this before launching third-party MIME handlers. Otherwise, if
+ // Thunderbird sets this in its startup script (as it does in Debian and
+ // Fedora), and Firefox does not set this in its startup script (it doesn't in
+ // Debian), then Firefox will think it is part of Thunderbird and try to make
+ // Thunderbird the default browser. See bug 1494436.
+ g_app_launch_context_unsetenv(context, "MOZ_APP_LAUNCHER");
+ if (aXDGToken) {
+ g_app_launch_context_setenv(context, "XDG_ACTIVATION_TOKEN", aXDGToken);
+ }
+ return context;
+}
+
+#ifdef __OpenBSD__
+// wrappers required for OpenBSD sandboxing with unveil()
+gboolean g_app_info_launch_uris_openbsd(GAppInfo* mApp, const char* uri,
+ GAppLaunchContext* context,
+ GError** error) {
+ gchar* path = g_filename_from_uri(uri, NULL, NULL);
+ auto releasePath = MakeScopeExit([&] { g_free(path); });
+ const gchar* bin = g_app_info_get_executable(mApp);
+ if (!bin) {
+ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "no executable found for %s, maybe not unveiled ?",
+ g_app_info_get_name(mApp));
+ return FALSE;
+ }
+ g_debug("spawning %s %s for %s", bin, path, uri);
+ const gchar* const argv[] = {bin, path, NULL};
+
+ GSpawnFlags flags =
+ static_cast<GSpawnFlags>(G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD);
+ gboolean result =
+ g_spawn_async(NULL, (char**)argv, NULL, flags, NULL, NULL, NULL, error);
+
+ if (!result) {
+ g_warning("Cannot launch application %s with arg %s: %s", bin, path,
+ (*error)->message);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+gboolean g_app_info_launch_default_for_uri_openbsd(const char* uri,
+ GAppLaunchContext* context,
+ GError** error) {
+ gboolean result_uncertain;
+ gchar* path = g_filename_from_uri(uri, NULL, NULL);
+ gchar* content_type = g_content_type_guess(path, NULL, 0, &result_uncertain);
+ gchar* scheme = g_uri_parse_scheme(uri);
+ auto release = MakeScopeExit([&] {
+ g_free(path);
+ g_free(content_type);
+ g_free(scheme);
+ });
+ if (g_strcmp0(scheme, "http") == 0 || g_strcmp0(scheme, "https") == 0)
+ return g_app_info_launch_default_for_uri(uri, context, error);
+
+ if (content_type != NULL && !result_uncertain) {
+ g_debug("content type for %s: %s", uri, content_type);
+ GAppInfo* app_info = g_app_info_get_default_for_type(content_type, false);
+ auto releaseAppInfo = MakeScopeExit([&] {
+ if (app_info) g_object_unref(app_info);
+ });
+ if (!app_info) {
+ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Could not find default handler for content type %s",
+ content_type);
+ return FALSE;
+ } else {
+ return g_app_info_launch_uris_openbsd(app_info, uri, context, error);
+ }
+ } else {
+ g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Could not find content type for URI: %s", uri);
+ return FALSE;
+ }
+}
+#endif
+
+static NS_IMETHODIMP LaunchWithURIImpl(RefPtr<GAppInfo> aInfo, nsIURI* aUri,
+ const char* aXDGToken = nullptr) {
+ GList uris = {0};
+ nsCString spec;
+ aUri->GetSpec(spec);
+ // nsPromiseFlatCString flatUri(aUri);
+ uris.data = const_cast<char*>(spec.get());
+
+ GUniquePtr<GError> error;
+#ifdef __OpenBSD__
+ gboolean result = g_app_info_launch_uris_openbsd(
+ aInfo, spec.get(), GetLaunchContext(aXDGToken).get(),
+ getter_Transfers(error));
+#else
+ gboolean result = g_app_info_launch_uris(
+ aInfo, &uris, GetLaunchContext(aXDGToken).get(), getter_Transfers(error));
+#endif
+ if (!result) {
+ g_warning("Cannot launch application: %s", error->message);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::LaunchWithURI(nsIURI* aUri,
+ mozilla::dom::BrowsingContext* aBrowsingContext) {
+ auto promise = mozilla::widget::RequestWaylandFocusPromise();
+ if (!promise) {
+ return LaunchWithURIImpl(mApp, aUri);
+ }
+ promise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ /* resolve */
+ [app = RefPtr{mApp}, uri = RefPtr{aUri}](nsCString token) {
+ LaunchWithURIImpl(app, uri, token.get());
+ },
+ /* reject */
+ [app = RefPtr{mApp}, uri = RefPtr{aUri}](bool state) {
+ LaunchWithURIImpl(app, uri);
+ });
+ return NS_OK;
+}
+
+class GIOUTF8StringEnumerator final : public nsStringEnumeratorBase {
+ ~GIOUTF8StringEnumerator() = default;
+
+ public:
+ GIOUTF8StringEnumerator() : mIndex(0) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+
+ using nsStringEnumeratorBase::GetNext;
+
+ nsTArray<nsCString> mStrings;
+ uint32_t mIndex;
+};
+
+NS_IMPL_ISUPPORTS(GIOUTF8StringEnumerator, nsIUTF8StringEnumerator,
+ nsIStringEnumerator)
+
+NS_IMETHODIMP
+GIOUTF8StringEnumerator::HasMore(bool* aResult) {
+ *aResult = mIndex < mStrings.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GIOUTF8StringEnumerator::GetNext(nsACString& aResult) {
+ if (mIndex >= mStrings.Length()) return NS_ERROR_UNEXPECTED;
+
+ aResult.Assign(mStrings[mIndex]);
+ ++mIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::GetSupportedURISchemes(nsIUTF8StringEnumerator** aSchemes) {
+ *aSchemes = nullptr;
+
+ RefPtr<GIOUTF8StringEnumerator> array = new GIOUTF8StringEnumerator();
+
+ GVfs* gvfs = g_vfs_get_default();
+
+ if (!gvfs) {
+ g_warning("Cannot get GVfs object.");
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ const gchar* const* uri_schemes = g_vfs_get_supported_uri_schemes(gvfs);
+
+ while (*uri_schemes != nullptr) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ array->mStrings.AppendElement(*uri_schemes);
+ uri_schemes++;
+ }
+
+ array.forget(aSchemes);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::SetAsDefaultForMimeType(nsACString const& aMimeType) {
+ GUniquePtr<char> content_type(
+ g_content_type_from_mime_type(PromiseFlatCString(aMimeType).get()));
+ if (!content_type) return NS_ERROR_FAILURE;
+ GUniquePtr<GError> error;
+ g_app_info_set_as_default_for_type(mApp, content_type.get(),
+ getter_Transfers(error));
+ if (error) {
+ g_warning("Cannot set application as default for MIME type (%s): %s",
+ PromiseFlatCString(aMimeType).get(), error->message);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+/**
+ * Set default application for files with given extensions
+ * @param fileExts string of space separated extensions
+ * @return NS_OK when application was set as default for given extensions,
+ * NS_ERROR_FAILURE otherwise
+ */
+NS_IMETHODIMP
+nsGIOMimeApp::SetAsDefaultForFileExtensions(nsACString const& fileExts) {
+ GUniquePtr<GError> error;
+ GUniquePtr<char> extensions(g_strdup(PromiseFlatCString(fileExts).get()));
+ char* ext_pos = extensions.get();
+ char* space_pos;
+
+ while ((space_pos = strchr(ext_pos, ' ')) || (*ext_pos != '\0')) {
+ if (space_pos) {
+ *space_pos = '\0';
+ }
+ g_app_info_set_as_default_for_extension(mApp, ext_pos,
+ getter_Transfers(error));
+ if (error) {
+ g_warning("Cannot set application as default for extension (%s): %s",
+ ext_pos, error->message);
+ return NS_ERROR_FAILURE;
+ }
+ if (space_pos) {
+ ext_pos = space_pos + 1;
+ } else {
+ *ext_pos = '\0';
+ }
+ }
+ return NS_OK;
+}
+
+/**
+ * Set default application for URI's of a particular scheme
+ * @param aURIScheme string containing the URI scheme
+ * @return NS_OK when application was set as default for URI scheme,
+ * NS_ERROR_FAILURE otherwise
+ */
+NS_IMETHODIMP
+nsGIOMimeApp::SetAsDefaultForURIScheme(nsACString const& aURIScheme) {
+ GUniquePtr<GError> error;
+ nsAutoCString contentType("x-scheme-handler/");
+ contentType.Append(aURIScheme);
+
+ g_app_info_set_as_default_for_type(mApp, contentType.get(),
+ getter_Transfers(error));
+ if (error) {
+ g_warning("Cannot set application as default for URI scheme (%s): %s",
+ PromiseFlatCString(aURIScheme).get(), error->message);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsGIOService, nsIGIOService)
+
+NS_IMETHODIMP
+nsGIOService::GetMimeTypeFromExtension(const nsACString& aExtension,
+ nsACString& aMimeType) {
+ nsAutoCString fileExtToUse("file.");
+ fileExtToUse.Append(aExtension);
+
+ gboolean result_uncertain;
+ GUniquePtr<char> content_type(
+ g_content_type_guess(fileExtToUse.get(), nullptr, 0, &result_uncertain));
+ if (!content_type) {
+ return NS_ERROR_FAILURE;
+ }
+
+ GUniquePtr<char> mime_type(g_content_type_get_mime_type(content_type.get()));
+ if (!mime_type) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aMimeType.Assign(mime_type.get());
+ return NS_OK;
+}
+// used in nsGNOMERegistry
+// -----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsGIOService::GetAppForURIScheme(const nsACString& aURIScheme,
+ nsIHandlerApp** aApp) {
+ *aApp = nullptr;
+
+ // Application in flatpak sandbox does not have access to the list
+ // of installed applications on the system. We use generic
+ // nsFlatpakHandlerApp which forwards launch call to the system.
+ if (widget::ShouldUsePortal(widget::PortalKind::MimeHandler)) {
+ if (mozilla::net::IsLoopbackHostname(aURIScheme)) {
+ // When the user writes foo:1234, we try to handle it natively using
+ // GetAppForURIScheme, and if that fails, we carry on. On flatpak there's
+ // no way to know if an app has handlers or not. Some things like
+ // localhost:1234 are really unlikely to be handled by native
+ // apps, and we're much better off returning an error here instead.
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<nsFlatpakHandlerApp> mozApp = new nsFlatpakHandlerApp();
+ mozApp.forget(aApp);
+ return NS_OK;
+ }
+
+ RefPtr<GAppInfo> app_info = dont_AddRef(g_app_info_get_default_for_uri_scheme(
+ PromiseFlatCString(aURIScheme).get()));
+ if (!app_info) {
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<nsGIOMimeApp> mozApp = new nsGIOMimeApp(app_info.forget());
+ mozApp.forget(aApp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOService::GetAppsForURIScheme(const nsACString& aURIScheme,
+ nsIMutableArray** aResult) {
+ // We don't need to return the nsFlatpakHandlerApp here because
+ // it would be skipped by the callers anyway.
+ // The preferred handler is provided by GetAppForURIScheme.
+ // This method returns all possible application handlers
+ // including preferred one. The callers skips the preferred
+ // handler in this list to avoid duplicate records in the list
+ // they create.
+ nsCOMPtr<nsIMutableArray> handlersArray =
+ do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+ nsAutoCString contentType("x-scheme-handler/");
+ contentType.Append(aURIScheme);
+
+ GList* appInfoList = g_app_info_get_all_for_type(contentType.get());
+ // g_app_info_get_all_for_type returns NULL when no appinfo is found
+ // or error occurs (contentType is NULL). We are fine with empty app list
+ // and we're sure that contentType is not NULL, so we won't return failure.
+ if (appInfoList) {
+ GList* appInfo = appInfoList;
+ while (appInfo) {
+ nsCOMPtr<nsIGIOMimeApp> mimeApp =
+ new nsGIOMimeApp(dont_AddRef(G_APP_INFO(appInfo->data)));
+ handlersArray->AppendElement(mimeApp);
+ appInfo = appInfo->next;
+ }
+ g_list_free(appInfoList);
+ }
+ handlersArray.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOService::GetAppForMimeType(const nsACString& aMimeType,
+ nsIHandlerApp** aApp) {
+ *aApp = nullptr;
+
+ // Flatpak does not reveal installed application to the sandbox,
+ // we need to create generic system handler.
+ if (widget::ShouldUsePortal(widget::PortalKind::MimeHandler)) {
+ RefPtr<nsFlatpakHandlerApp> mozApp = new nsFlatpakHandlerApp();
+ mozApp.forget(aApp);
+ return NS_OK;
+ }
+
+ GUniquePtr<char> content_type(
+ g_content_type_from_mime_type(PromiseFlatCString(aMimeType).get()));
+ if (!content_type) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // GIO returns "unknown" appinfo for the application/octet-stream, which is
+ // useless. It's better to fallback to create appinfo from file extension
+ // later.
+ if (g_content_type_is_unknown(content_type.get())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<GAppInfo> app_info =
+ dont_AddRef(g_app_info_get_default_for_type(content_type.get(), false));
+ if (!app_info) {
+ return NS_ERROR_FAILURE;
+ }
+#ifdef __OpenBSD__
+ char* t;
+ t = g_find_program_in_path(g_app_info_get_executable(app_info));
+ if (t != nullptr) {
+ g_debug("%s is registered as handler for %s, binary available as %s",
+ g_app_info_get_executable(app_info), content_type.get(), t);
+ } else {
+ g_warning(
+ "%s is registered as handler for %s but not available in PATH "
+ "(missing unveil ?)",
+ g_app_info_get_executable(app_info), content_type.get());
+ }
+#endif
+ RefPtr<nsGIOMimeApp> mozApp = new nsGIOMimeApp(app_info.forget());
+ mozApp.forget(aApp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOService::GetDescriptionForMimeType(const nsACString& aMimeType,
+ nsACString& aDescription) {
+ GUniquePtr<char> content_type(
+ g_content_type_from_mime_type(PromiseFlatCString(aMimeType).get()));
+ if (!content_type) {
+ return NS_ERROR_FAILURE;
+ }
+
+ GUniquePtr<char> desc(g_content_type_get_description(content_type.get()));
+ if (!desc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aDescription.Assign(desc.get());
+ return NS_OK;
+}
+
+static nsresult ShowURIImpl(nsIURI* aURI, const char* aXDGToken = nullptr) {
+ nsAutoCString spec;
+ MOZ_TRY(aURI->GetSpec(spec));
+ GUniquePtr<GError> error;
+#ifdef __OpenBSD__
+ if (!g_app_info_launch_default_for_uri_openbsd(
+ spec.get(), GetLaunchContext(aXDGToken).get(),
+#else
+ if (!g_app_info_launch_default_for_uri(spec.get(),
+ GetLaunchContext(aXDGToken).get(),
+#endif
+ getter_Transfers(error))) {
+ g_warning("Could not launch default application for URI: %s",
+ error->message);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult nsGIOService::ShowURI(nsIURI* aURI) {
+ auto promise = mozilla::widget::RequestWaylandFocusPromise();
+ if (!promise) {
+ return ShowURIImpl(aURI);
+ }
+ promise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ /* resolve */
+ [uri = RefPtr{aURI}](nsCString token) { ShowURIImpl(uri, token.get()); },
+ /* reject */
+ [uri = RefPtr{aURI}](bool state) { ShowURIImpl(uri); });
+ return NS_OK;
+}
+
+static nsresult LaunchPathImpl(const nsACString& aPath,
+ const char* aXDGToken = nullptr) {
+ RefPtr<GFile> file = dont_AddRef(
+ g_file_new_for_commandline_arg(PromiseFlatCString(aPath).get()));
+ GUniquePtr<char> spec(g_file_get_uri(file));
+ GUniquePtr<GError> error;
+#ifdef __OpenBSD__
+ g_app_info_launch_default_for_uri_openbsd(spec.get(),
+ GetLaunchContext(aXDGToken).get(),
+#else
+ g_app_info_launch_default_for_uri(spec.get(),
+ GetLaunchContext(aXDGToken).get(),
+#endif
+ getter_Transfers(error));
+ if (error) {
+ g_warning("Cannot launch default application: %s", error->message);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+static nsresult LaunchPath(const nsACString& aPath) {
+ auto promise = mozilla::widget::RequestWaylandFocusPromise();
+ if (!promise) {
+ return LaunchPathImpl(aPath);
+ }
+ promise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ /* resolve */
+ [path = nsCString{aPath}](nsCString token) {
+ LaunchPathImpl(path, token.get());
+ },
+ /* reject */
+ [path = nsCString{aPath}](bool state) { LaunchPathImpl(path); });
+ return NS_OK;
+}
+
+nsresult nsGIOService::LaunchFile(const nsACString& aPath) {
+ return LaunchPath(aPath);
+}
+
+nsresult nsGIOService::GetIsRunningUnderFlatpak(bool* aResult) {
+ *aResult = mozilla::widget::IsRunningUnderFlatpak();
+ return NS_OK;
+}
+
+nsresult nsGIOService::GetIsRunningUnderSnap(bool* aResult) {
+ *aResult = mozilla::widget::IsRunningUnderSnap();
+ return NS_OK;
+}
+
+static nsresult RevealDirectory(nsIFile* aFile, bool aForce) {
+ nsAutoCString path;
+ if (bool isDir; NS_SUCCEEDED(aFile->IsDirectory(&isDir)) && isDir) {
+ MOZ_TRY(aFile->GetNativePath(path));
+ return LaunchPath(path);
+ }
+
+ if (!aForce) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> parentDir;
+ MOZ_TRY(aFile->GetParent(getter_AddRefs(parentDir)));
+ MOZ_TRY(parentDir->GetNativePath(path));
+ return LaunchPath(path);
+}
+
+#ifdef MOZ_ENABLE_DBUS
+// Classic DBus
+const char kFreedesktopFileManagerName[] = "org.freedesktop.FileManager1";
+const char kFreedesktopFileManagerPath[] = "/org/freedesktop/FileManager1";
+const char kMethodShowItems[] = "ShowItems";
+
+// Portal for Snap, Flatpak
+const char kFreedesktopPortalName[] = "org.freedesktop.portal.Desktop";
+const char kFreedesktopPortalPath[] = "/org/freedesktop/portal/desktop";
+const char kFreedesktopPortalOpenURI[] = "org.freedesktop.portal.OpenURI";
+const char kMethodOpenDirectory[] = "OpenDirectory";
+
+static nsresult RevealFileViaDBusWithProxy(GDBusProxy* aProxy, nsIFile* aFile,
+ const char* aMethod) {
+ nsAutoCString path;
+ MOZ_TRY(aFile->GetNativePath(path));
+
+ RefPtr<mozilla::widget::DBusCallPromise> dbusPromise;
+ const char* startupId = "";
+
+ const int32_t timeout =
+ StaticPrefs::widget_gtk_file_manager_show_items_timeout_ms();
+
+ if (!(strcmp(aMethod, kMethodOpenDirectory) == 0)) {
+ GUniquePtr<gchar> uri(g_filename_to_uri(path.get(), nullptr, nullptr));
+ if (!uri) {
+ RevealDirectory(aFile, /* aForce = */ true);
+ return NS_ERROR_FAILURE;
+ }
+
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_STRING_ARRAY);
+ g_variant_builder_add(&builder, "s", uri.get());
+
+ RefPtr<GVariant> variant = dont_AddRef(
+ g_variant_ref_sink(g_variant_new("(ass)", &builder, startupId)));
+ g_variant_builder_clear(&builder);
+
+ dbusPromise = widget::DBusProxyCall(aProxy, aMethod, variant,
+ G_DBUS_CALL_FLAGS_NONE, timeout);
+ } else {
+ int fd = open(path.get(), O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ g_printerr("Failed to open file: %s returned %d\n", path.get(), errno);
+ RevealDirectory(aFile, /* aForce = */ true);
+ return NS_ERROR_FAILURE;
+ }
+
+ GVariantBuilder options;
+ g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
+
+ static auto g_unix_fd_list_new_from_array =
+ (GUnixFDList * (*)(const gint* fds, gint n_fds))
+ dlsym(RTLD_DEFAULT, "g_unix_fd_list_new_from_array");
+
+ // Will take ownership of the fd, so we dont have to care about it anymore
+ RefPtr<GUnixFDList> fd_list =
+ dont_AddRef(g_unix_fd_list_new_from_array(&fd, 1));
+
+ RefPtr<GVariant> variant = dont_AddRef(
+ g_variant_ref_sink(g_variant_new("(sha{sv})", startupId, 0, &options)));
+ g_variant_builder_clear(&options);
+
+ dbusPromise = widget::DBusProxyCallWithUnixFDList(
+ aProxy, aMethod, variant, G_DBUS_CALL_FLAGS_NONE, timeout, fd_list);
+ }
+
+ dbusPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [](RefPtr<GVariant>&& aResult) {
+ // Do nothing, file is shown, we're done.
+ },
+ [file = RefPtr{aFile}, aMethod](GUniquePtr<GError>&& aError) {
+ g_printerr("Failed to query file manager via %s: %s\n", aMethod,
+ aError->message);
+ RevealDirectory(file, /* aForce = */ true);
+ });
+ return NS_OK;
+}
+
+static void RevealFileViaDBus(nsIFile* aFile, const char* aName,
+ const char* aPath, const char* aCall,
+ const char* aMethod) {
+ widget::CreateDBusProxyForBus(
+ G_BUS_TYPE_SESSION,
+ GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES),
+ /* aInterfaceInfo = */ nullptr, aName, aPath, aCall)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [file = RefPtr{aFile}, aMethod](RefPtr<GDBusProxy>&& aProxy) {
+ RevealFileViaDBusWithProxy(aProxy.get(), file, aMethod);
+ },
+ [file = RefPtr{aFile}, aName](GUniquePtr<GError>&& aError) {
+ g_printerr("Failed to create DBUS proxy for %s: %s\n", aName,
+ aError->message);
+ RevealDirectory(file, /* aForce = */ true);
+ });
+}
+
+static void RevealFileViaDBusClassic(nsIFile* aFile) {
+ RevealFileViaDBus(aFile, kFreedesktopFileManagerName,
+ kFreedesktopFileManagerPath, kFreedesktopFileManagerName,
+ kMethodShowItems);
+}
+
+static void RevealFileViaDBusPortal(nsIFile* aFile) {
+ RevealFileViaDBus(aFile, kFreedesktopPortalName, kFreedesktopPortalPath,
+ kFreedesktopPortalOpenURI, kMethodOpenDirectory);
+}
+#endif
+
+nsresult nsGIOService::RevealFile(nsIFile* aFile) {
+#ifdef MOZ_ENABLE_DBUS
+ if (NS_SUCCEEDED(RevealDirectory(aFile, /* aForce = */ false))) {
+ return NS_OK;
+ }
+ if (ShouldUsePortal(widget::PortalKind::OpenUri)) {
+ RevealFileViaDBusPortal(aFile);
+ } else {
+ RevealFileViaDBusClassic(aFile);
+ }
+ return NS_OK;
+#else
+ return RevealDirectory(aFile, /* aForce = */ true);
+#endif
+}
+
+/**
+ * Find GIO Mime App from given commandline.
+ * This is different from CreateAppFromCommand because instead of creating the
+ * GIO Mime App in case it's not found in the GIO application list, the method
+ * returns error.
+ * @param aCmd command with parameters used to start the application
+ * @return NS_OK when application is found, NS_ERROR_NOT_AVAILABLE otherwise
+ */
+NS_IMETHODIMP
+nsGIOService::FindAppFromCommand(nsACString const& aCmd,
+ nsIGIOMimeApp** aAppInfo) {
+ RefPtr<GAppInfo> app_info;
+
+ GList* apps = g_app_info_get_all();
+
+ // Try to find relevant and existing GAppInfo in all installed application
+ // We do this by comparing each GAppInfo's executable with out own
+ for (GList* node = apps; node; node = node->next) {
+ RefPtr<GAppInfo> app_info_from_list = dont_AddRef((GAppInfo*)node->data);
+ node->data = nullptr;
+ if (!app_info) {
+ // If the executable is not absolute, get it's full path
+ GUniquePtr<char> executable(g_find_program_in_path(
+ g_app_info_get_executable(app_info_from_list)));
+
+ if (executable &&
+ strcmp(executable.get(), PromiseFlatCString(aCmd).get()) == 0) {
+ app_info = std::move(app_info_from_list);
+ // Can't break here because we need to keep iterating to unref the other
+ // nodes.
+ }
+ }
+ }
+
+ g_list_free(apps);
+ if (!app_info) {
+ *aAppInfo = nullptr;
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ RefPtr<nsGIOMimeApp> app = new nsGIOMimeApp(app_info.forget());
+ app.forget(aAppInfo);
+ return NS_OK;
+}
+
+/**
+ * Create application info for specified command and application name.
+ * Command arguments are ignored and the "%u" is always added.
+ * @param cmd command to execute
+ * @param appName application name
+ * @param appInfo location where created GAppInfo is stored
+ * @return NS_OK when object is created, NS_ERROR_FILE_NOT_FOUND when executable
+ * is not found in the system path or NS_ERROR_FAILURE otherwise.
+ */
+NS_IMETHODIMP
+nsGIOService::CreateAppFromCommand(nsACString const& cmd,
+ nsACString const& appName,
+ nsIGIOMimeApp** appInfo) {
+ *appInfo = nullptr;
+
+ // Using G_APP_INFO_CREATE_SUPPORTS_URIS calling
+ // g_app_info_create_from_commandline appends %u to the cmd even when cmd
+ // already contains this parameter. To avoid that we're going to remove
+ // arguments before passing to it.
+ nsAutoCString commandWithoutArgs;
+ nsresult rv = GetCommandFromCommandline(cmd, commandWithoutArgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ GUniquePtr<GError> error;
+ RefPtr<GAppInfo> app_info = dont_AddRef(g_app_info_create_from_commandline(
+ commandWithoutArgs.BeginReading(), PromiseFlatCString(appName).get(),
+ G_APP_INFO_CREATE_SUPPORTS_URIS, getter_Transfers(error)));
+ if (!app_info) {
+ g_warning("Cannot create application info from command: %s",
+ error->message);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Check if executable exist in path
+ GUniquePtr<gchar> executableWithFullPath(
+ g_find_program_in_path(commandWithoutArgs.BeginReading()));
+ if (!executableWithFullPath) {
+ return NS_ERROR_FILE_NOT_FOUND;
+ }
+ RefPtr<nsGIOMimeApp> mozApp = new nsGIOMimeApp(app_info.forget());
+ mozApp.forget(appInfo);
+ return NS_OK;
+}