summaryrefslogtreecommitdiffstats
path: root/toolkit/system/gnome
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/system/gnome')
-rw-r--r--toolkit/system/gnome/components.conf28
-rw-r--r--toolkit/system/gnome/moz.build31
-rw-r--r--toolkit/system/gnome/nsAlertsIconListener.cpp363
-rw-r--r--toolkit/system/gnome/nsAlertsIconListener.h105
-rw-r--r--toolkit/system/gnome/nsGIOService.cpp951
-rw-r--r--toolkit/system/gnome/nsGIOService.h26
-rw-r--r--toolkit/system/gnome/nsGSettingsService.cpp316
-rw-r--r--toolkit/system/gnome/nsGSettingsService.h29
-rw-r--r--toolkit/system/gnome/nsSystemAlertsService.cpp131
-rw-r--r--toolkit/system/gnome/nsSystemAlertsService.h42
10 files changed, 2022 insertions, 0 deletions
diff --git a/toolkit/system/gnome/components.conf b/toolkit/system/gnome/components.conf
new file mode 100644
index 0000000000..1aa0ab1048
--- /dev/null
+++ b/toolkit/system/gnome/components.conf
@@ -0,0 +1,28 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{e3a1f3c9-3ae1-4b40-a5e0-7b457fc9a9ad}',
+ 'contract_ids': ['@mozilla.org/gio-service;1'],
+ 'type': 'nsGIOService',
+ 'headers': ['/toolkit/system/gnome/nsGIOService.h'],
+ },
+ {
+ 'cid': '{bfd4a9d8-d886-4161-81ef-8868da114170}',
+ 'contract_ids': ['@mozilla.org/gsettings-service;1'],
+ 'type': 'nsGSettingsService',
+ 'headers': ['/toolkit/system/gnome/nsGSettingsService.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{84e11f80-ca55-11dd-ad8b-0800200c9a66}',
+ 'contract_ids': ['@mozilla.org/system-alerts-service;1'],
+ 'type': 'nsSystemAlertsService',
+ 'headers': ['/toolkit/system/gnome/nsSystemAlertsService.h'],
+ 'init_method': 'Init',
+ },
+]
diff --git a/toolkit/system/gnome/moz.build b/toolkit/system/gnome/moz.build
new file mode 100644
index 0000000000..aa2df2b66b
--- /dev/null
+++ b/toolkit/system/gnome/moz.build
@@ -0,0 +1,31 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Firefox", "Shell Integration")
+
+SOURCES += [
+ "nsAlertsIconListener.cpp",
+ "nsSystemAlertsService.cpp",
+]
+
+SOURCES += [
+ "nsGIOService.cpp",
+ "nsGSettingsService.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/toolkit/components/build/",
+]
+
+CXXFLAGS += CONFIG["GLIB_CFLAGS"]
+CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
diff --git a/toolkit/system/gnome/nsAlertsIconListener.cpp b/toolkit/system/gnome/nsAlertsIconListener.cpp
new file mode 100644
index 0000000000..06152fd4d5
--- /dev/null
+++ b/toolkit/system/gnome/nsAlertsIconListener.cpp
@@ -0,0 +1,363 @@
+/* -*- 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 "nsAlertsIconListener.h"
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSystemAlertsService.h"
+#include "nsIAlertsService.h"
+#include "nsICancelable.h"
+#include "nsImageToPixbuf.h"
+#include "nsIStringBundle.h"
+#include "nsIObserverService.h"
+#include "nsCRT.h"
+#include "mozilla/XREAppData.h"
+#include "mozilla/GRefPtr.h"
+#include "mozilla/GUniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+#include <dlfcn.h>
+#include <gdk/gdk.h>
+
+using namespace mozilla;
+extern const StaticXREAppData* gAppData;
+
+static bool gHasActions = false;
+static bool gHasCaps = false;
+
+void* nsAlertsIconListener::libNotifyHandle = nullptr;
+bool nsAlertsIconListener::libNotifyNotAvail = false;
+nsAlertsIconListener::notify_is_initted_t
+ nsAlertsIconListener::notify_is_initted = nullptr;
+nsAlertsIconListener::notify_init_t nsAlertsIconListener::notify_init = nullptr;
+nsAlertsIconListener::notify_get_server_caps_t
+ nsAlertsIconListener::notify_get_server_caps = nullptr;
+nsAlertsIconListener::notify_notification_new_t
+ nsAlertsIconListener::notify_notification_new = nullptr;
+nsAlertsIconListener::notify_notification_show_t
+ nsAlertsIconListener::notify_notification_show = nullptr;
+nsAlertsIconListener::notify_notification_set_icon_from_pixbuf_t
+ nsAlertsIconListener::notify_notification_set_icon_from_pixbuf = nullptr;
+nsAlertsIconListener::notify_notification_add_action_t
+ nsAlertsIconListener::notify_notification_add_action = nullptr;
+nsAlertsIconListener::notify_notification_close_t
+ nsAlertsIconListener::notify_notification_close = nullptr;
+nsAlertsIconListener::notify_notification_set_hint_t
+ nsAlertsIconListener::notify_notification_set_hint = nullptr;
+nsAlertsIconListener::notify_notification_set_timeout_t
+ nsAlertsIconListener::notify_notification_set_timeout = nullptr;
+
+static void notify_action_cb(NotifyNotification* notification, gchar* action,
+ gpointer user_data) {
+ nsAlertsIconListener* alert = static_cast<nsAlertsIconListener*>(user_data);
+ alert->SendCallback();
+}
+
+static void notify_closed_marshal(GClosure* closure, GValue* return_value,
+ guint n_param_values,
+ const GValue* param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data) {
+ MOZ_ASSERT(n_param_values >= 1, "No object in params");
+
+ nsAlertsIconListener* alert =
+ static_cast<nsAlertsIconListener*>(closure->data);
+ alert->SendClosed();
+ NS_RELEASE(alert);
+}
+
+static already_AddRefed<GdkPixbuf> GetPixbufFromImgRequest(
+ imgIRequest* aRequest) {
+ nsCOMPtr<imgIContainer> image;
+ nsresult rv = aRequest->GetImage(getter_AddRefs(image));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ int32_t width = 0, height = 0;
+ const int32_t kBytesPerPixel = 4;
+ // DBUS_MAXIMUM_ARRAY_LENGTH is 64M, there is 60 bytes overhead
+ // for the hints array with only the image payload, 256 is used to give
+ // some breathing room.
+ const int32_t kMaxImageBytes = 64 * 1024 * 1024 - 256;
+ image->GetWidth(&width);
+ image->GetHeight(&height);
+ if (width * height * kBytesPerPixel > kMaxImageBytes) {
+ // The image won't fit in a dbus array
+ return nullptr;
+ }
+
+ return nsImageToPixbuf::ImageToPixbuf(image);
+}
+
+NS_IMPL_ISUPPORTS(nsAlertsIconListener, nsIAlertNotificationImageListener,
+ nsIObserver, nsISupportsWeakReference)
+
+nsAlertsIconListener::nsAlertsIconListener(nsSystemAlertsService* aBackend,
+ const nsAString& aAlertName)
+ : mAlertName(aAlertName), mBackend(aBackend), mNotification(nullptr) {
+ if (!libNotifyHandle && !libNotifyNotAvail) {
+ libNotifyHandle = dlopen("libnotify.so.4", RTLD_LAZY);
+ if (!libNotifyHandle) {
+ libNotifyHandle = dlopen("libnotify.so.1", RTLD_LAZY);
+ if (!libNotifyHandle) {
+ libNotifyNotAvail = true;
+ return;
+ }
+ }
+
+ notify_is_initted =
+ (notify_is_initted_t)dlsym(libNotifyHandle, "notify_is_initted");
+ notify_init = (notify_init_t)dlsym(libNotifyHandle, "notify_init");
+ notify_get_server_caps = (notify_get_server_caps_t)dlsym(
+ libNotifyHandle, "notify_get_server_caps");
+ notify_notification_new = (notify_notification_new_t)dlsym(
+ libNotifyHandle, "notify_notification_new");
+ notify_notification_show = (notify_notification_show_t)dlsym(
+ libNotifyHandle, "notify_notification_show");
+ notify_notification_set_icon_from_pixbuf =
+ (notify_notification_set_icon_from_pixbuf_t)dlsym(
+ libNotifyHandle, "notify_notification_set_icon_from_pixbuf");
+ notify_notification_add_action = (notify_notification_add_action_t)dlsym(
+ libNotifyHandle, "notify_notification_add_action");
+ notify_notification_close = (notify_notification_close_t)dlsym(
+ libNotifyHandle, "notify_notification_close");
+ notify_notification_set_hint = (notify_notification_set_hint_t)dlsym(
+ libNotifyHandle, "notify_notification_set_hint");
+ notify_notification_set_timeout = (notify_notification_set_timeout_t)dlsym(
+ libNotifyHandle, "notify_notification_set_timeout");
+ if (!notify_is_initted || !notify_init || !notify_get_server_caps ||
+ !notify_notification_new || !notify_notification_show ||
+ !notify_notification_set_icon_from_pixbuf ||
+ !notify_notification_add_action || !notify_notification_close) {
+ dlclose(libNotifyHandle);
+ libNotifyHandle = nullptr;
+ }
+ }
+}
+
+nsAlertsIconListener::~nsAlertsIconListener() {
+ mBackend->RemoveListener(mAlertName, this);
+ // Don't dlclose libnotify as it uses atexit().
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::OnImageMissing(nsISupports*) {
+ // This notification doesn't have an image, or there was an error getting
+ // the image. Show the notification without an icon.
+ return ShowAlert(nullptr);
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::OnImageReady(nsISupports*, imgIRequest* aRequest) {
+ RefPtr<GdkPixbuf> imagePixbuf = GetPixbufFromImgRequest(aRequest);
+ ShowAlert(imagePixbuf);
+ return NS_OK;
+}
+
+nsresult nsAlertsIconListener::ShowAlert(GdkPixbuf* aPixbuf) {
+ if (!mBackend->IsActiveListener(mAlertName, this)) return NS_OK;
+
+ mNotification = notify_notification_new(mAlertTitle.get(), mAlertText.get(),
+ nullptr, nullptr);
+
+ if (!mNotification) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsCOMPtr<nsIObserverService> obsServ =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (obsServ) obsServ->AddObserver(this, "quit-application", true);
+
+ if (aPixbuf) notify_notification_set_icon_from_pixbuf(mNotification, aPixbuf);
+
+ NS_ADDREF(this);
+ if (mAlertHasAction) {
+ // What we put as the label doesn't matter here, if the action
+ // string is "default" then that makes the entire bubble clickable
+ // rather than creating a button.
+ notify_notification_add_action(mNotification, "default", "Activate",
+ notify_action_cb, this, nullptr);
+ }
+
+ if (notify_notification_set_hint) {
+ notify_notification_set_hint(mNotification, "suppress-sound",
+ g_variant_new_boolean(mAlertIsSilent));
+
+ // If MOZ_DESKTOP_FILE_NAME variable is set, use it as the application id,
+ // otherwise use gAppData->name
+ if (getenv("MOZ_DESKTOP_FILE_NAME")) {
+ // Send the desktop name to identify the application
+ // The desktop-entry is the part before the .desktop
+ notify_notification_set_hint(
+ mNotification, "desktop-entry",
+ g_variant_new("s", getenv("MOZ_DESKTOP_FILE_NAME")));
+ } else {
+ notify_notification_set_hint(mNotification, "desktop-entry",
+ g_variant_new("s", gAppData->remotingName));
+ }
+ }
+
+ if (notify_notification_set_timeout && mAlertRequiresInteraction) {
+ constexpr gint kNotifyExpiresNever = 0;
+ notify_notification_set_timeout(mNotification, kNotifyExpiresNever);
+ }
+
+ // Fedora 10 calls NotifyNotification "closed" signal handlers with a
+ // different signature, so a marshaller is used instead of a C callback to
+ // get the user_data (this) in a parseable format. |closure| is created
+ // with a floating reference, which gets sunk by g_signal_connect_closure().
+ GClosure* closure = g_closure_new_simple(sizeof(GClosure), this);
+ g_closure_set_marshal(closure, notify_closed_marshal);
+ mClosureHandler =
+ g_signal_connect_closure(mNotification, "closed", closure, FALSE);
+ GUniquePtr<GError> error;
+ if (!notify_notification_show(mNotification, getter_Transfers(error))) {
+ NS_WARNING(error->message);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mAlertListener)
+ mAlertListener->Observe(nullptr, "alertshow", mAlertCookie.get());
+
+ return NS_OK;
+}
+
+void nsAlertsIconListener::SendCallback() {
+ if (mAlertListener)
+ mAlertListener->Observe(nullptr, "alertclickcallback", mAlertCookie.get());
+}
+
+void nsAlertsIconListener::SendClosed() {
+ if (mNotification) {
+ g_object_unref(mNotification);
+ mNotification = nullptr;
+ }
+ NotifyFinished();
+}
+
+NS_IMETHODIMP
+nsAlertsIconListener::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ // We need to close any open notifications upon application exit, otherwise
+ // we will leak since libnotify holds a ref for us.
+ if (!nsCRT::strcmp(aTopic, "quit-application") && mNotification) {
+ g_signal_handler_disconnect(mNotification, mClosureHandler);
+ g_object_unref(mNotification);
+ mNotification = nullptr;
+ Release(); // equivalent to NS_RELEASE(this)
+ }
+ return NS_OK;
+}
+
+nsresult nsAlertsIconListener::Close() {
+ if (mIconRequest) {
+ mIconRequest->Cancel(NS_BINDING_ABORTED);
+ mIconRequest = nullptr;
+ }
+
+ if (!mNotification) {
+ NotifyFinished();
+ return NS_OK;
+ }
+
+ GUniquePtr<GError> error;
+ if (!notify_notification_close(mNotification, getter_Transfers(error))) {
+ NS_WARNING(error->message);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAlertsIconListener::InitAlertAsync(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener) {
+ if (!libNotifyHandle) return NS_ERROR_FAILURE;
+
+ if (!notify_is_initted()) {
+ // Give the name of this application to libnotify
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+
+ nsAutoCString appShortName;
+ if (bundleService) {
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(bundle));
+ nsAutoString appName;
+
+ if (bundle) {
+ bundle->GetStringFromName("brandShortName", appName);
+ CopyUTF16toUTF8(appName, appShortName);
+ } else {
+ NS_WARNING(
+ "brand.properties not present, using default application name");
+ appShortName.AssignLiteral("Mozilla");
+ }
+ } else {
+ appShortName.AssignLiteral("Mozilla");
+ }
+
+ if (!notify_init(appShortName.get())) return NS_ERROR_FAILURE;
+
+ GList* server_caps = notify_get_server_caps();
+ if (server_caps) {
+ gHasCaps = true;
+ for (GList* cap = server_caps; cap != nullptr; cap = cap->next) {
+ if (!strcmp((char*)cap->data, "actions")) {
+ gHasActions = true;
+ break;
+ }
+ }
+ g_list_foreach(server_caps, (GFunc)g_free, nullptr);
+ g_list_free(server_caps);
+ }
+ }
+
+ if (!gHasCaps) {
+ // if notify_get_server_caps() failed above we need to assume
+ // there is no notification-server to display anything
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = aAlert->GetTextClickable(&mAlertHasAction);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!gHasActions && mAlertHasAction)
+ return NS_ERROR_FAILURE; // No good, fallback to XUL
+
+ rv = aAlert->GetSilent(&mAlertIsSilent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aAlert->GetRequireInteraction(&mAlertRequiresInteraction);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString title;
+ rv = aAlert->GetTitle(title);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Workaround for a libnotify bug - blank titles aren't dealt with
+ // properly so we use a space
+ if (title.IsEmpty()) {
+ mAlertTitle = " "_ns;
+ } else {
+ CopyUTF16toUTF8(title, mAlertTitle);
+ }
+
+ nsAutoString text;
+ rv = aAlert->GetText(text);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF16toUTF8(text, mAlertText);
+
+ mAlertListener = aAlertListener;
+
+ rv = aAlert->GetCookie(mAlertCookie);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aAlert->LoadImage(/* aTimeout = */ 0, this, /* aUserData = */ nullptr,
+ getter_AddRefs(mIconRequest));
+}
+
+void nsAlertsIconListener::NotifyFinished() {
+ if (mAlertListener)
+ mAlertListener->Observe(nullptr, "alertfinished", mAlertCookie.get());
+}
diff --git a/toolkit/system/gnome/nsAlertsIconListener.h b/toolkit/system/gnome/nsAlertsIconListener.h
new file mode 100644
index 0000000000..e0ef6cffe1
--- /dev/null
+++ b/toolkit/system/gnome/nsAlertsIconListener.h
@@ -0,0 +1,105 @@
+/* -*- 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/. */
+
+#ifndef nsAlertsIconListener_h__
+#define nsAlertsIconListener_h__
+
+#include "nsCOMPtr.h"
+#include "nsIAlertsService.h"
+#include "nsString.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+class nsIAlertNotification;
+class nsICancelable;
+class nsSystemAlertsService;
+
+struct NotifyNotification;
+
+class nsAlertsIconListener : public nsIAlertNotificationImageListener,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIALERTNOTIFICATIONIMAGELISTENER
+ NS_DECL_NSIOBSERVER
+
+ nsAlertsIconListener(nsSystemAlertsService* aBackend,
+ const nsAString& aAlertName);
+
+ nsresult InitAlertAsync(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener);
+ nsresult Close();
+
+ void SendCallback();
+ void SendClosed();
+
+ protected:
+ virtual ~nsAlertsIconListener();
+
+ /**
+ * The only difference between libnotify.so.4 and libnotify.so.1 for these
+ * symbols is that notify_notification_new takes three arguments in
+ * libnotify.so.4 and four in libnotify.so.1. Passing the fourth argument as
+ * NULL is binary compatible.
+ */
+ using NotifyActionCallback = void (*)(NotifyNotification*, char*, gpointer);
+ using notify_is_initted_t = bool (*)();
+ using notify_init_t = bool (*)(const char*);
+ using notify_get_server_caps_t = GList* (*)();
+ using notify_notification_new_t = NotifyNotification* (*)(const char*,
+ const char*,
+ const char*,
+ const char*);
+ using notify_notification_show_t = bool (*)(void*, GError**);
+ using notify_notification_set_icon_from_pixbuf_t = void (*)(void*,
+ GdkPixbuf*);
+ using notify_notification_add_action_t = void (*)(void*, const char*,
+ const char*,
+ NotifyActionCallback,
+ gpointer, GFreeFunc);
+ using notify_notification_close_t = bool (*)(void*, GError**);
+ using notify_notification_set_hint_t = void (*)(NotifyNotification*,
+ const char*, GVariant*);
+ using notify_notification_set_timeout_t = void (*)(NotifyNotification*, gint);
+
+ nsCOMPtr<nsICancelable> mIconRequest;
+ nsCString mAlertTitle;
+ nsCString mAlertText;
+
+ nsCOMPtr<nsIObserver> mAlertListener;
+ nsString mAlertCookie;
+ nsString mAlertName;
+
+ RefPtr<nsSystemAlertsService> mBackend;
+
+ bool mAlertHasAction;
+ bool mAlertIsSilent;
+ bool mAlertRequiresInteraction;
+
+ static void* libNotifyHandle;
+ static bool libNotifyNotAvail;
+ static notify_is_initted_t notify_is_initted;
+ static notify_init_t notify_init;
+ static notify_get_server_caps_t notify_get_server_caps;
+ static notify_notification_new_t notify_notification_new;
+ static notify_notification_show_t notify_notification_show;
+ static notify_notification_set_icon_from_pixbuf_t
+ notify_notification_set_icon_from_pixbuf;
+ static notify_notification_add_action_t notify_notification_add_action;
+ static notify_notification_close_t notify_notification_close;
+ static notify_notification_set_hint_t notify_notification_set_hint;
+ static notify_notification_set_timeout_t notify_notification_set_timeout;
+ NotifyNotification* mNotification;
+ gulong mClosureHandler;
+
+ nsresult ShowAlert(GdkPixbuf* aPixbuf);
+
+ void NotifyFinished();
+};
+
+#endif
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;
+}
diff --git a/toolkit/system/gnome/nsGIOService.h b/toolkit/system/gnome/nsGIOService.h
new file mode 100644
index 0000000000..e83c724de2
--- /dev/null
+++ b/toolkit/system/gnome/nsGIOService.h
@@ -0,0 +1,26 @@
+/* -*- 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/. */
+
+#ifndef nsGIOService_h_
+#define nsGIOService_h_
+
+#include "nsIGIOService.h"
+
+#define NS_GIOSERVICE_CID \
+ { \
+ 0xe3a1f3c9, 0x3ae1, 0x4b40, { \
+ 0xa5, 0xe0, 0x7b, 0x45, 0x7f, 0xc9, 0xa9, 0xad \
+ } \
+ }
+
+class nsGIOService final : public nsIGIOService {
+ ~nsGIOService() = default;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGIOSERVICE
+};
+
+#endif
diff --git a/toolkit/system/gnome/nsGSettingsService.cpp b/toolkit/system/gnome/nsGSettingsService.cpp
new file mode 100644
index 0000000000..73cba5c947
--- /dev/null
+++ b/toolkit/system/gnome/nsGSettingsService.cpp
@@ -0,0 +1,316 @@
+/* -*- 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 "nsGSettingsService.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "prlink.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMutableArray.h"
+#include "nsISupportsPrimitives.h"
+
+#include <glib.h>
+#include <glib-object.h>
+
+using namespace mozilla;
+
+typedef struct _GSettings GSettings;
+typedef struct _GVariantType GVariantType;
+typedef struct _GVariant GVariant;
+
+#ifndef G_VARIANT_TYPE_INT32
+# define G_VARIANT_TYPE_INT32 ((const GVariantType*)"i")
+# define G_VARIANT_TYPE_BOOLEAN ((const GVariantType*)"b")
+# define G_VARIANT_TYPE_STRING ((const GVariantType*)"s")
+# define G_VARIANT_TYPE_OBJECT_PATH ((const GVariantType*)"o")
+# define G_VARIANT_TYPE_SIGNATURE ((const GVariantType*)"g")
+#endif
+#ifndef G_VARIANT_TYPE_STRING_ARRAY
+# define G_VARIANT_TYPE_STRING_ARRAY ((const GVariantType*)"as")
+#endif
+
+#define GSETTINGS_FUNCTIONS \
+ FUNC(g_settings_new, GSettings*, (const char* schema)) \
+ FUNC(g_settings_list_schemas, const char* const*, (void)) \
+ FUNC(g_settings_list_keys, char**, (GSettings * settings)) \
+ FUNC(g_settings_get_value, GVariant*, \
+ (GSettings * settings, const char* key)) \
+ FUNC(g_settings_set_value, gboolean, \
+ (GSettings * settings, const char* key, GVariant* value)) \
+ FUNC(g_settings_range_check, gboolean, \
+ (GSettings * settings, const char* key, GVariant* value)) \
+ FUNC(g_variant_get_int32, gint32, (GVariant * variant)) \
+ FUNC(g_variant_get_boolean, gboolean, (GVariant * variant)) \
+ FUNC(g_variant_get_string, const char*, (GVariant * value, gsize * length)) \
+ FUNC(g_variant_get_strv, const char**, (GVariant * value, gsize * length)) \
+ FUNC(g_variant_is_of_type, gboolean, \
+ (GVariant * value, const GVariantType* type)) \
+ FUNC(g_variant_new_int32, GVariant*, (gint32 value)) \
+ FUNC(g_variant_new_boolean, GVariant*, (gboolean value)) \
+ FUNC(g_variant_new_string, GVariant*, (const char* string)) \
+ FUNC(g_variant_unref, void, (GVariant * value))
+
+#define FUNC(name, type, params) \
+ typedef type(*_##name##_fn) params; \
+ static _##name##_fn _##name;
+
+GSETTINGS_FUNCTIONS
+
+#undef FUNC
+
+#define g_settings_new _g_settings_new
+#define g_settings_list_schemas _g_settings_list_schemas
+#define g_settings_list_keys _g_settings_list_keys
+#define g_settings_get_value _g_settings_get_value
+#define g_settings_set_value _g_settings_set_value
+#define g_settings_range_check _g_settings_range_check
+#define g_variant_get_int32 _g_variant_get_int32
+#define g_variant_get_boolean _g_variant_get_boolean
+#define g_variant_get_string _g_variant_get_string
+#define g_variant_get_strv _g_variant_get_strv
+#define g_variant_is_of_type _g_variant_is_of_type
+#define g_variant_new_int32 _g_variant_new_int32
+#define g_variant_new_boolean _g_variant_new_boolean
+#define g_variant_new_string _g_variant_new_string
+#define g_variant_unref _g_variant_unref
+
+static PRLibrary* gioLib = nullptr;
+
+class nsGSettingsCollection final : public nsIGSettingsCollection {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGSETTINGSCOLLECTION
+
+ explicit nsGSettingsCollection(GSettings* aSettings)
+ : mSettings(aSettings), mKeys(nullptr) {}
+
+ private:
+ ~nsGSettingsCollection();
+
+ bool KeyExists(const nsACString& aKey);
+ bool SetValue(const nsACString& aKey, GVariant* aValue);
+
+ GSettings* mSettings;
+ char** mKeys;
+};
+
+nsGSettingsCollection::~nsGSettingsCollection() {
+ g_strfreev(mKeys);
+ g_object_unref(mSettings);
+}
+
+bool nsGSettingsCollection::KeyExists(const nsACString& aKey) {
+ if (!mKeys) mKeys = g_settings_list_keys(mSettings);
+
+ for (uint32_t i = 0; mKeys[i] != nullptr; i++) {
+ if (aKey.Equals(mKeys[i])) return true;
+ }
+
+ return false;
+}
+
+bool nsGSettingsCollection::SetValue(const nsACString& aKey, GVariant* aValue) {
+ if (!KeyExists(aKey) ||
+ !g_settings_range_check(mSettings, PromiseFlatCString(aKey).get(),
+ aValue)) {
+ g_variant_unref(aValue);
+ return false;
+ }
+
+ return g_settings_set_value(mSettings, PromiseFlatCString(aKey).get(),
+ aValue);
+}
+
+NS_IMPL_ISUPPORTS(nsGSettingsCollection, nsIGSettingsCollection)
+
+NS_IMETHODIMP
+nsGSettingsCollection::SetString(const nsACString& aKey,
+ const nsACString& aValue) {
+ GVariant* value = g_variant_new_string(PromiseFlatCString(aValue).get());
+ if (!value) return NS_ERROR_OUT_OF_MEMORY;
+
+ bool res = SetValue(aKey, value);
+
+ return res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::SetBoolean(const nsACString& aKey, bool aValue) {
+ GVariant* value = g_variant_new_boolean(aValue);
+ if (!value) return NS_ERROR_OUT_OF_MEMORY;
+
+ bool res = SetValue(aKey, value);
+
+ return res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::SetInt(const nsACString& aKey, int32_t aValue) {
+ GVariant* value = g_variant_new_int32(aValue);
+ if (!value) return NS_ERROR_OUT_OF_MEMORY;
+
+ bool res = SetValue(aKey, value);
+
+ return res ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::GetString(const nsACString& aKey, nsACString& aResult) {
+ if (!KeyExists(aKey)) return NS_ERROR_INVALID_ARG;
+
+ GVariant* value =
+ g_settings_get_value(mSettings, PromiseFlatCString(aKey).get());
+ if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING) &&
+ !g_variant_is_of_type(value, G_VARIANT_TYPE_OBJECT_PATH) &&
+ !g_variant_is_of_type(value, G_VARIANT_TYPE_SIGNATURE)) {
+ g_variant_unref(value);
+ return NS_ERROR_FAILURE;
+ }
+
+ aResult.Assign(g_variant_get_string(value, nullptr));
+ g_variant_unref(value);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::GetBoolean(const nsACString& aKey, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!KeyExists(aKey)) return NS_ERROR_INVALID_ARG;
+
+ GVariant* value =
+ g_settings_get_value(mSettings, PromiseFlatCString(aKey).get());
+ if (!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) {
+ g_variant_unref(value);
+ return NS_ERROR_FAILURE;
+ }
+
+ gboolean res = g_variant_get_boolean(value);
+ *aResult = res ? true : false;
+ g_variant_unref(value);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGSettingsCollection::GetInt(const nsACString& aKey, int32_t* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!KeyExists(aKey)) return NS_ERROR_INVALID_ARG;
+
+ GVariant* value =
+ g_settings_get_value(mSettings, PromiseFlatCString(aKey).get());
+ if (!g_variant_is_of_type(value, G_VARIANT_TYPE_INT32)) {
+ g_variant_unref(value);
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = g_variant_get_int32(value);
+ g_variant_unref(value);
+
+ return NS_OK;
+}
+
+// These types are local to nsGSettingsService::Init, but ISO C++98 doesn't
+// allow a template (ArrayLength) to be instantiated based on a local type.
+// Boo-urns!
+typedef void (*nsGSettingsFunc)();
+struct nsGSettingsDynamicFunction {
+ const char* functionName;
+ nsGSettingsFunc* function;
+};
+
+NS_IMETHODIMP
+nsGSettingsCollection::GetStringList(const nsACString& aKey,
+ nsIArray** aResult) {
+ if (!KeyExists(aKey)) return NS_ERROR_INVALID_ARG;
+
+ nsCOMPtr<nsIMutableArray> items(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ if (!items) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ GVariant* value =
+ g_settings_get_value(mSettings, PromiseFlatCString(aKey).get());
+
+ if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING_ARRAY)) {
+ g_variant_unref(value);
+ return NS_ERROR_FAILURE;
+ }
+
+ const gchar** gs_strings = g_variant_get_strv(value, nullptr);
+ if (!gs_strings) {
+ // empty array
+ items.forget(aResult);
+ g_variant_unref(value);
+ return NS_OK;
+ }
+
+ const gchar** p_gs_strings = gs_strings;
+ while (*p_gs_strings != nullptr) {
+ nsCOMPtr<nsISupportsCString> obj(
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+ if (obj) {
+ obj->SetData(nsDependentCString(*p_gs_strings));
+ items->AppendElement(obj);
+ }
+ p_gs_strings++;
+ }
+ g_free(gs_strings);
+ items.forget(aResult);
+ g_variant_unref(value);
+ return NS_OK;
+}
+
+nsresult nsGSettingsService::Init() {
+#define FUNC(name, type, params) {#name, (nsGSettingsFunc*)&_##name},
+ static const nsGSettingsDynamicFunction kGSettingsSymbols[] = {
+ GSETTINGS_FUNCTIONS};
+#undef FUNC
+
+ if (!gioLib) {
+ gioLib = PR_LoadLibrary("libgio-2.0.so.0");
+ if (!gioLib) return NS_ERROR_FAILURE;
+ }
+
+ for (auto GSettingsSymbol : kGSettingsSymbols) {
+ *GSettingsSymbol.function =
+ PR_FindFunctionSymbol(gioLib, GSettingsSymbol.functionName);
+ if (!*GSettingsSymbol.function) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsGSettingsService, nsIGSettingsService)
+
+nsGSettingsService::~nsGSettingsService() {
+ if (gioLib) {
+ PR_UnloadLibrary(gioLib);
+ gioLib = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsGSettingsService::GetCollectionForSchema(
+ const nsACString& schema, nsIGSettingsCollection** collection) {
+ NS_ENSURE_ARG_POINTER(collection);
+
+ const char* const* schemas = g_settings_list_schemas();
+
+ for (uint32_t i = 0; schemas[i] != nullptr; i++) {
+ if (schema.Equals(schemas[i])) {
+ GSettings* settings = g_settings_new(PromiseFlatCString(schema).get());
+ nsGSettingsCollection* mozGSettings = new nsGSettingsCollection(settings);
+ NS_ADDREF(*collection = mozGSettings);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
diff --git a/toolkit/system/gnome/nsGSettingsService.h b/toolkit/system/gnome/nsGSettingsService.h
new file mode 100644
index 0000000000..1eb592e4aa
--- /dev/null
+++ b/toolkit/system/gnome/nsGSettingsService.h
@@ -0,0 +1,29 @@
+/* -*- 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/. */
+
+#ifndef nsGSettingsService_h_
+#define nsGSettingsService_h_
+
+#include "nsIGSettingsService.h"
+
+#define NS_GSETTINGSSERVICE_CID \
+ { \
+ 0xbfd4a9d8, 0xd886, 0x4161, { \
+ 0x81, 0xef, 0x88, 0x68, 0xda, 0x11, 0x41, 0x70 \
+ } \
+ }
+
+class nsGSettingsService final : public nsIGSettingsService {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGSETTINGSSERVICE
+
+ nsresult Init();
+
+ private:
+ ~nsGSettingsService();
+};
+
+#endif
diff --git a/toolkit/system/gnome/nsSystemAlertsService.cpp b/toolkit/system/gnome/nsSystemAlertsService.cpp
new file mode 100644
index 0000000000..870eb029f7
--- /dev/null
+++ b/toolkit/system/gnome/nsSystemAlertsService.cpp
@@ -0,0 +1,131 @@
+/* -*- 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 "nsComponentManagerUtils.h"
+#include "nsSystemAlertsService.h"
+#include "nsAlertsIconListener.h"
+
+NS_IMPL_ADDREF(nsSystemAlertsService)
+NS_IMPL_RELEASE(nsSystemAlertsService)
+
+NS_INTERFACE_MAP_BEGIN(nsSystemAlertsService)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAlertsService)
+ NS_INTERFACE_MAP_ENTRY(nsIAlertsService)
+ NS_INTERFACE_MAP_ENTRY(nsIAlertsDoNotDisturb)
+NS_INTERFACE_MAP_END
+
+nsSystemAlertsService::nsSystemAlertsService() = default;
+
+nsSystemAlertsService::~nsSystemAlertsService() = default;
+
+nsresult nsSystemAlertsService::Init() { return NS_OK; }
+
+NS_IMETHODIMP nsSystemAlertsService::ShowAlertNotification(
+ const nsAString& aImageUrl, const nsAString& aAlertTitle,
+ const nsAString& aAlertText, bool aAlertTextClickable,
+ const nsAString& aAlertCookie, nsIObserver* aAlertListener,
+ const nsAString& aAlertName, const nsAString& aBidi, const nsAString& aLang,
+ const nsAString& aData, nsIPrincipal* aPrincipal, bool aInPrivateBrowsing,
+ bool aRequireInteraction) {
+ nsCOMPtr<nsIAlertNotification> alert =
+ do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
+ NS_ENSURE_TRUE(alert, NS_ERROR_FAILURE);
+ // vibrate is unused for now
+ nsTArray<uint32_t> vibrate;
+ nsresult rv = alert->Init(aAlertName, aImageUrl, aAlertTitle, aAlertText,
+ aAlertTextClickable, aAlertCookie, aBidi, aLang,
+ aData, aPrincipal, aInPrivateBrowsing,
+ aRequireInteraction, false, vibrate);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ShowAlert(alert, aAlertListener);
+}
+
+NS_IMETHODIMP nsSystemAlertsService::ShowPersistentNotification(
+ const nsAString& aPersistentData, nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener) {
+ return ShowAlert(aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP nsSystemAlertsService::ShowAlert(nsIAlertNotification* aAlert,
+ nsIObserver* aAlertListener) {
+ NS_ENSURE_ARG(aAlert);
+
+ nsAutoString alertName;
+ nsresult rv = aAlert->GetName(alertName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsAlertsIconListener> alertListener =
+ new nsAlertsIconListener(this, alertName);
+ if (!alertListener) return NS_ERROR_OUT_OF_MEMORY;
+
+ if (mSuppressForScreenSharing) {
+ alertListener->SendClosed();
+ return NS_OK;
+ }
+
+ AddListener(alertName, alertListener);
+ return alertListener->InitAlertAsync(aAlert, aAlertListener);
+}
+
+NS_IMETHODIMP nsSystemAlertsService::CloseAlert(const nsAString& aAlertName,
+ bool aContextClosed) {
+ RefPtr<nsAlertsIconListener> listener = mActiveListeners.Get(aAlertName);
+ if (!listener) {
+ return NS_OK;
+ }
+ mActiveListeners.Remove(aAlertName);
+ return listener->Close();
+}
+
+NS_IMETHODIMP nsSystemAlertsService::GetManualDoNotDisturb(bool* aRetVal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsSystemAlertsService::SetManualDoNotDisturb(bool aDoNotDisturb) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsSystemAlertsService::GetSuppressForScreenSharing(
+ bool* aRetVal) {
+ NS_ENSURE_ARG(aRetVal);
+ *aRetVal = mSuppressForScreenSharing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSystemAlertsService::SetSuppressForScreenSharing(
+ bool aSuppress) {
+ mSuppressForScreenSharing = aSuppress;
+ return NS_OK;
+}
+
+bool nsSystemAlertsService::IsActiveListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener) {
+ return mActiveListeners.Get(aAlertName) == aListener;
+}
+
+void nsSystemAlertsService::AddListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener) {
+ const auto oldListener =
+ mActiveListeners.WithEntryHandle(aAlertName, [&](auto&& entry) {
+ RefPtr<nsAlertsIconListener> oldListener =
+ entry ? entry.Data() : nullptr;
+ entry.InsertOrUpdate(aListener);
+ return oldListener;
+ });
+ if (oldListener) {
+ // If an alert with this name already exists, close it.
+ oldListener->Close();
+ }
+}
+
+void nsSystemAlertsService::RemoveListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener) {
+ auto entry = mActiveListeners.Lookup(aAlertName);
+ if (entry && entry.Data() == aListener) {
+ // The alert may have been replaced; only remove it from the active
+ // listeners map if it's the same.
+ entry.Remove();
+ }
+}
diff --git a/toolkit/system/gnome/nsSystemAlertsService.h b/toolkit/system/gnome/nsSystemAlertsService.h
new file mode 100644
index 0000000000..d24168e9af
--- /dev/null
+++ b/toolkit/system/gnome/nsSystemAlertsService.h
@@ -0,0 +1,42 @@
+/* -*- 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/. */
+
+#ifndef nsSystemAlertsService_h__
+#define nsSystemAlertsService_h__
+
+#include "nsIAlertsService.h"
+#include "nsTHashMap.h"
+
+class nsAlertsIconListener;
+
+class nsSystemAlertsService : public nsIAlertsService,
+ public nsIAlertsDoNotDisturb {
+ public:
+ NS_DECL_NSIALERTSSERVICE
+ NS_DECL_NSIALERTSDONOTDISTURB
+ NS_DECL_ISUPPORTS
+
+ nsSystemAlertsService();
+
+ nsresult Init();
+
+ bool IsActiveListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener);
+ void RemoveListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener);
+
+ protected:
+ virtual ~nsSystemAlertsService();
+
+ void AddListener(const nsAString& aAlertName,
+ nsAlertsIconListener* aListener);
+
+ nsTHashMap<nsStringHashKey, nsAlertsIconListener*> mActiveListeners;
+
+ private:
+ bool mSuppressForScreenSharing = false;
+};
+
+#endif /* nsSystemAlertsService_h__ */