summarylogtreecommitdiffstats
path: root/0025-Add-KDE-integration-to-Firefox-toolkit-parts.patch
diff options
context:
space:
mode:
Diffstat (limited to '0025-Add-KDE-integration-to-Firefox-toolkit-parts.patch')
-rw-r--r--0025-Add-KDE-integration-to-Firefox-toolkit-parts.patch1442
1 files changed, 1442 insertions, 0 deletions
diff --git a/0025-Add-KDE-integration-to-Firefox-toolkit-parts.patch b/0025-Add-KDE-integration-to-Firefox-toolkit-parts.patch
new file mode 100644
index 000000000000..c8cbf2bbf019
--- /dev/null
+++ b/0025-Add-KDE-integration-to-Firefox-toolkit-parts.patch
@@ -0,0 +1,1442 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Wolfgang Rosenauer <wolfgang@rosenauer.org>
+Date: Tue, 8 Aug 2023 16:13:48 +0300
+Subject: [PATCH] Add KDE integration to Firefox (toolkit parts)
+
+Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=140751
+Bug: https://bugzilla.suse.com/show_bug.cgi?id=170055
+
+Co-authored-by: Wolfgang Rosenauer <wolfgang@rosenauer.org>
+Co-authored-by: Lubos Lunak <lunak@suse.com>
+---
+ modules/libpref/Preferences.cpp | 13 +-
+ modules/libpref/moz.build | 4 +
+ python/mozbuild/mozpack/chrome/flags.py | 1 +
+ python/mozbuild/mozpack/chrome/manifest.py | 1 +
+ toolkit/components/downloads/moz.build | 4 +
+ .../mozapps/downloads/HelperAppDlg.sys.mjs | 70 +++--
+ .../unixproxy/nsUnixSystemProxySettings.cpp | 29 ++
+ toolkit/xre/moz.build | 2 +
+ toolkit/xre/nsKDEUtils.cpp | 286 ++++++++++++++++++
+ toolkit/xre/nsKDEUtils.h | 53 ++++
+ uriloader/exthandler/HandlerServiceParent.cpp | 6 +-
+ uriloader/exthandler/moz.build | 3 +
+ .../exthandler/unix/nsCommonRegistry.cpp | 42 +++
+ uriloader/exthandler/unix/nsCommonRegistry.h | 28 ++
+ uriloader/exthandler/unix/nsKDERegistry.cpp | 75 +++++
+ uriloader/exthandler/unix/nsKDERegistry.h | 35 +++
+ uriloader/exthandler/unix/nsMIMEInfoUnix.cpp | 28 +-
+ .../exthandler/unix/nsOSHelperAppService.cpp | 10 +-
+ widget/gtk/moz.build | 1 +
+ widget/gtk/nsFilePicker.cpp | 230 +++++++++++++-
+ widget/gtk/nsFilePicker.h | 6 +
+ xpcom/components/ManifestParser.cpp | 10 +
+ xpcom/components/moz.build | 1 +
+ xpcom/io/nsLocalFileUnix.cpp | 20 +-
+ 24 files changed, 921 insertions(+), 37 deletions(-)
+ create mode 100644 toolkit/xre/nsKDEUtils.cpp
+ create mode 100644 toolkit/xre/nsKDEUtils.h
+ create mode 100644 uriloader/exthandler/unix/nsCommonRegistry.cpp
+ create mode 100644 uriloader/exthandler/unix/nsCommonRegistry.h
+ create mode 100644 uriloader/exthandler/unix/nsKDERegistry.cpp
+ create mode 100644 uriloader/exthandler/unix/nsKDERegistry.h
+
+diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp
+index 9e0853d1a5b7cf9dd05a15db3499dd18fcf73803..1ab9be4e7415ddd19f393b6856c13c462059b111 100644
+--- a/modules/libpref/Preferences.cpp
++++ b/modules/libpref/Preferences.cpp
+@@ -95,6 +95,7 @@
+ #ifdef MOZ_BACKGROUNDTASKS
+ # include "mozilla/BackgroundTasks.h"
+ #endif
++#include "nsKDEUtils.h"
+
+ #ifdef DEBUG
+ # include <map>
+@@ -4906,6 +4907,16 @@ nsresult Preferences::InitInitialObjects(bool aIsStartup) {
+ #endif
+ };
+
++ if (nsKDEUtils::kdeSession()) { // TODO what if some setup actually requires
++ // the helper?
++ for (int i = 0; i < MOZ_ARRAY_LENGTH(specialFiles); ++i) {
++ if (*specialFiles[i] == '\0') {
++ specialFiles[i] = "kde.js";
++ break;
++ }
++ }
++ }
++
+ rv = pref_LoadPrefsInDir(defaultPrefDir, specialFiles,
+ ArrayLength(specialFiles));
+ if (NS_FAILED(rv)) {
+@@ -4980,7 +4991,7 @@ nsresult Preferences::InitInitialObjects(bool aIsStartup) {
+ }
+
+ // Do we care if a file provided by this process fails to load?
+- pref_LoadPrefsInDir(path, nullptr, 0);
++ pref_LoadPrefsInDir(path, specialFiles, ArrayLength(specialFiles));
+ }
+ }
+
+diff --git a/modules/libpref/moz.build b/modules/libpref/moz.build
+index e8f8b97170d32c1d3ac342dd93da7265bf707c8f..831001cee4b1eb33171d83d524ee9e453a800257 100644
+--- a/modules/libpref/moz.build
++++ b/modules/libpref/moz.build
+@@ -126,6 +126,10 @@ UNIFIED_SOURCES += [
+ "SharedPrefMap.cpp",
+ ]
+
++LOCAL_INCLUDES += [
++ '/toolkit/xre'
++]
++
+ gen_all_tuple = tuple(gen_h + gen_cpp + gen_rs)
+
+ GeneratedFile(
+diff --git a/python/mozbuild/mozpack/chrome/flags.py b/python/mozbuild/mozpack/chrome/flags.py
+index 6b096c862aaac5e02d9d7dacda42d9321d5e89cc..2b46d9294b93fda17117e9c84b240c52f96c9b74 100644
+--- a/python/mozbuild/mozpack/chrome/flags.py
++++ b/python/mozbuild/mozpack/chrome/flags.py
+@@ -234,6 +234,7 @@ class Flags(OrderedDict):
+ "tablet": Flag,
+ "process": StringFlag,
+ "backgroundtask": StringFlag,
++ "desktop": StringFlag,
+ }
+ RE = re.compile(r"([!<>=]+)")
+
+diff --git a/python/mozbuild/mozpack/chrome/manifest.py b/python/mozbuild/mozpack/chrome/manifest.py
+index 14c11d4c1daa8cbb03abf3cd2e1a7b60a981abc8..41b9969e7277fa2400f299863c83145342cd7b43 100644
+--- a/python/mozbuild/mozpack/chrome/manifest.py
++++ b/python/mozbuild/mozpack/chrome/manifest.py
+@@ -43,6 +43,7 @@ class ManifestEntry(object):
+ "process",
+ "contentaccessible",
+ "backgroundtask",
++ "desktop",
+ ]
+
+ def __init__(self, base, *flags):
+diff --git a/toolkit/components/downloads/moz.build b/toolkit/components/downloads/moz.build
+index 3818e8c0db1ed3cfc068d89b18b1fe2f1bf750a9..b70986db811191952919531cfb79e04b801491a2 100644
+--- a/toolkit/components/downloads/moz.build
++++ b/toolkit/components/downloads/moz.build
+@@ -51,5 +51,9 @@ if CONFIG["MOZ_PLACES"]:
+
+ FINAL_LIBRARY = "xul"
+
++LOCAL_INCLUDES += [
++ '/toolkit/xre'
++]
++
+ with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "Downloads API")
+diff --git a/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs b/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs
+index 66f77d38e4ed7b3802303194e8df675a5db81272..f8839c446683620d6df6c6eb2ea0a0ca3549af95 100644
+--- a/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs
++++ b/toolkit/mozapps/downloads/HelperAppDlg.sys.mjs
+@@ -1246,26 +1246,56 @@ nsUnknownContentTypeDialog.prototype = {
+ this.chosenApp = params.handlerApp;
+ }
+ } else if ("@mozilla.org/applicationchooser;1" in Cc) {
+- var nsIApplicationChooser = Ci.nsIApplicationChooser;
+- var appChooser = Cc["@mozilla.org/applicationchooser;1"].createInstance(
+- nsIApplicationChooser
+- );
+- appChooser.init(
+- this.mDialog,
+- this.dialogElement("strings").getString("chooseAppFilePickerTitle")
+- );
+- var contentTypeDialogObj = this;
+- let appChooserCallback = function appChooserCallback_done(aResult) {
+- if (aResult) {
+- contentTypeDialogObj.chosenApp = aResult.QueryInterface(
+- Ci.nsILocalHandlerApp
+- );
+- }
+- contentTypeDialogObj.finishChooseApp();
+- };
+- appChooser.open(this.mLauncher.MIMEInfo.MIMEType, appChooserCallback);
+- // The finishChooseApp is called from appChooserCallback
+- return;
++ // handle the KDE case which is implemented in the filepicker
++ // therefore falling back to Gtk2 like behaviour if KDE is running
++ // FIXME this should be better handled in the nsIApplicationChooser
++ // interface
++ var env = Components.classes["@mozilla.org/process/environment;1"]
++ .getService(Components.interfaces.nsIEnvironment);
++ if (env.get('KDE_FULL_SESSION') == "true")
++ {
++ var nsIFilePicker = Ci.nsIFilePicker;
++ var fp = Cc["@mozilla.org/filepicker;1"]
++ .createInstance(nsIFilePicker);
++ fp.init(this.mDialog,
++ this.dialogElement("strings").getString("chooseAppFilePickerTitle"),
++ nsIFilePicker.modeOpen);
++
++ fp.appendFilters(nsIFilePicker.filterApps);
++
++ fp.open(aResult => {
++ if (aResult == nsIFilePicker.returnOK && fp.file) {
++ // Remember the file they chose to run.
++ var localHandlerApp =
++ Cc["@mozilla.org/uriloader/local-handler-app;1"].
++ createInstance(Ci.nsILocalHandlerApp);
++ localHandlerApp.executable = fp.file;
++ this.chosenApp = localHandlerApp;
++ }
++ this.finishChooseApp();
++ });
++ } else {
++ var nsIApplicationChooser = Ci.nsIApplicationChooser;
++ var appChooser = Cc["@mozilla.org/applicationchooser;1"].createInstance(
++ nsIApplicationChooser
++ );
++ appChooser.init(
++ this.mDialog,
++ this.dialogElement("strings").getString("chooseAppFilePickerTitle")
++ );
++ var contentTypeDialogObj = this;
++ let appChooserCallback = function appChooserCallback_done(aResult) {
++ if (aResult) {
++ contentTypeDialogObj.chosenApp = aResult.QueryInterface(
++ Ci.nsILocalHandlerApp
++ );
++ }
++ contentTypeDialogObj.finishChooseApp();
++ };
++ appChooser.open(this.mLauncher.MIMEInfo.MIMEType, appChooserCallback);
++ // The finishChooseApp is called from appChooserCallback
++ return;
++ }
+ } else {
+ var nsIFilePicker = Ci.nsIFilePicker;
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+diff --git a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
+index 185dc1e22a903cec95b212d1713dddf764b9b198..bdb4ed6f9f86583d02dd80278f858d064584f82a 100644
+--- a/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
++++ b/toolkit/system/unixproxy/nsUnixSystemProxySettings.cpp
+@@ -16,6 +16,8 @@
+ #include "nsISupportsPrimitives.h"
+ #include "nsIGSettingsService.h"
+ #include "nsReadableUtils.h"
++#include "nsPrintfCString.h"
++#include "nsKDEUtils.h"
+
+ using namespace mozilla;
+
+@@ -39,6 +41,8 @@ class nsUnixSystemProxySettings final : public nsISystemProxySettings {
+ nsACString& aResult);
+ nsresult SetProxyResultFromGSettings(const char* aKeyBase, const char* aType,
+ nsACString& aResult);
++ nsresult GetProxyFromKDE(const nsACString& aScheme, const nsACString& aHost,
++ PRInt32 aPort, nsACString& aResult);
+ };
+
+ NS_IMPL_ISUPPORTS(nsUnixSystemProxySettings, nsISystemProxySettings)
+@@ -397,6 +401,9 @@ nsresult nsUnixSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
+ const nsACString& aHost,
+ const int32_t aPort,
+ nsACString& aResult) {
++ if (nsKDEUtils::kdeSupport())
++ return GetProxyFromKDE(aScheme, aHost, aPort, aResult);
++
+ if (mProxySettings) {
+ nsresult rv = GetProxyFromGSettings(aScheme, aHost, aPort, aResult);
+ if (NS_SUCCEEDED(rv)) return rv;
+@@ -405,6 +412,28 @@ nsresult nsUnixSystemProxySettings::GetProxyForURI(const nsACString& aSpec,
+ return GetProxyFromEnvironment(aScheme, aHost, aPort, aResult);
+ }
+
++nsresult nsUnixSystemProxySettings::GetProxyFromKDE(const nsACString& aScheme,
++ const nsACString& aHost,
++ PRInt32 aPort,
++ nsACString& aResult) {
++ nsAutoCString url;
++ url = aScheme;
++ url += "://";
++ url += aHost;
++ if (aPort >= 0) {
++ url += ":";
++ url += nsPrintfCString("%d", aPort);
++ }
++ nsTArray<nsCString> command;
++ command.AppendElement("GETPROXY"_ns);
++ command.AppendElement(url);
++ nsTArray<nsCString> result;
++ if (!nsKDEUtils::command(command, &result) || result.Length() != 1)
++ return NS_ERROR_FAILURE;
++ aResult = result[0];
++ return NS_OK;
++}
++
+ NS_IMPL_COMPONENT_FACTORY(nsUnixSystemProxySettings) {
+ auto result = MakeRefPtr<nsUnixSystemProxySettings>();
+ result->Init();
+diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build
+index de05e0cc29752855138b4d189ce6a13c2121d715..c89faad7bfca4ab1d60390766b2e7befd9e3831d 100644
+--- a/toolkit/xre/moz.build
++++ b/toolkit/xre/moz.build
+@@ -96,7 +96,9 @@ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit":
+ "UIKitDirProvider.mm",
+ ]
+ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
++ EXPORTS += ['nsKDEUtils.h']
+ UNIFIED_SOURCES += [
++ "nsKDEUtils.cpp",
+ "nsNativeAppSupportUnix.cpp",
+ ]
+ CXXFLAGS += CONFIG["MOZ_X11_SM_CFLAGS"]
+diff --git a/toolkit/xre/nsKDEUtils.cpp b/toolkit/xre/nsKDEUtils.cpp
+new file mode 100644
+index 0000000000000000000000000000000000000000..e282de40618e0be06a4247891d9ab1a26cba2126
+--- /dev/null
++++ b/toolkit/xre/nsKDEUtils.cpp
+@@ -0,0 +1,286 @@
++/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
++/* 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 "nsKDEUtils.h"
++#include "nsIWidget.h"
++#include "nsISupportsPrimitives.h"
++#include "nsIMutableArray.h"
++#include "nsComponentManagerUtils.h"
++#include "nsArrayUtils.h"
++
++#include <gtk/gtk.h>
++
++#include <limits.h>
++#include <stdio.h>
++#include <sys/wait.h>
++#include <sys/resource.h>
++#include <unistd.h>
++#include <X11/Xlib.h>
++// copied from X11/X.h as a hack since for an unknown
++// reason it's not picked up from X11/X.h
++#ifndef None
++# define None 0L /* universal null resource or null atom */
++#endif
++
++// #define DEBUG_KDE
++#ifdef DEBUG_KDE
++# define KMOZILLAHELPER "kmozillahelper"
++#else
++// not need for lib64, it's a binary
++# define KMOZILLAHELPER "/usr/lib/mozilla/kmozillahelper"
++#endif
++
++#define KMOZILLAHELPER_VERSION 6
++#define MAKE_STR2(n) #n
++#define MAKE_STR(n) MAKE_STR2(n)
++
++static bool getKdeSession() {
++ if (PR_GetEnv("KDE_FULL_SESSION")) {
++ return true;
++ }
++ return false;
++}
++
++static bool getKdeSupport() {
++ nsTArray<nsCString> command;
++ command.AppendElement("CHECK"_ns);
++ command.AppendElement("KMOZILLAHELPER_VERSION"_ns);
++ bool kde = nsKDEUtils::command(command);
++#ifdef DEBUG_KDE
++ fprintf(stderr, "KDE RUNNING %d\n", kde);
++#endif
++ return kde;
++}
++
++nsKDEUtils::nsKDEUtils() : commandFile(NULL), replyFile(NULL) {}
++
++nsKDEUtils::~nsKDEUtils() {
++ // closeHelper(); not actually useful, exiting will close the fd too
++}
++
++nsKDEUtils* nsKDEUtils::self() {
++ static nsKDEUtils s;
++ return &s;
++}
++
++static bool helperRunning = false;
++static bool helperFailed = false;
++
++bool nsKDEUtils::kdeSession() {
++ static bool session = getKdeSession();
++ return session;
++}
++
++bool nsKDEUtils::kdeSupport() {
++ static bool support = kdeSession() && getKdeSupport();
++ return support && helperRunning;
++}
++
++struct nsKDECommandData {
++ FILE* file;
++ nsTArray<nsCString>* output;
++ GMainLoop* loop;
++ bool success;
++};
++
++static gboolean kdeReadFunc(GIOChannel*, GIOCondition, gpointer data) {
++ nsKDECommandData* p = static_cast<nsKDECommandData*>(data);
++ char buf[8192]; // TODO big enough
++ bool command_done = false;
++ bool command_failed = false;
++ while (!command_done && !command_failed &&
++ fgets(buf, 8192, p->file) !=
++ NULL) { // TODO what if the kernel splits a line into two chunks?
++ // #ifdef DEBUG_KDE
++ // fprintf( stderr, "READ: %s %d\n", buf, feof( p->file ));
++ // #endif
++ if (char* eol = strchr(buf, '\n')) *eol = '\0';
++ command_done = (strcmp(buf, "\\1") == 0);
++ command_failed = (strcmp(buf, "\\0") == 0);
++ nsAutoCString line(buf);
++ line.ReplaceSubstring("\\n", "\n");
++ line.ReplaceSubstring(
++ "\\"
++ "\\",
++ "\\"); // \\ -> \ , i.e. unescape
++ if (p->output && !(command_done || command_failed))
++ p->output->AppendElement(nsCString(buf)); // TODO utf8?
++ }
++ bool quit = false;
++ if (feof(p->file) || command_failed) {
++ quit = true;
++ p->success = false;
++ }
++ if (command_done) { // reading one reply finished
++ quit = true;
++ p->success = true;
++ }
++ if (quit) {
++ if (p->loop) g_main_loop_quit(p->loop);
++ return FALSE;
++ }
++ return TRUE;
++}
++
++bool nsKDEUtils::command(const nsTArray<nsCString>& command,
++ nsTArray<nsCString>* output) {
++ return self()->internalCommand(command, NULL, false, output);
++}
++
++bool nsKDEUtils::command(nsIArray* command, nsIArray** output) {
++ nsTArray<nsCString> in;
++ PRUint32 length;
++ command->GetLength(&length);
++ for (PRUint32 i = 0; i < length; i++) {
++ nsCOMPtr<nsISupportsCString> str = do_QueryElementAt(command, i);
++ if (str) {
++ nsAutoCString s;
++ str->GetData(s);
++ in.AppendElement(s);
++ }
++ }
++
++ nsTArray<nsCString> out;
++ bool ret = self()->internalCommand(in, NULL, false, &out);
++
++ if (!output) return ret;
++
++ nsCOMPtr<nsIMutableArray> result = do_CreateInstance(NS_ARRAY_CONTRACTID);
++ if (!result) return false;
++
++ for (PRUint32 i = 0; i < out.Length(); i++) {
++ nsCOMPtr<nsISupportsCString> rstr =
++ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
++ if (!rstr) return false;
++
++ rstr->SetData(out[i]);
++ result->AppendElement(rstr);
++ }
++
++ NS_ADDREF(*output = result);
++ return ret;
++}
++
++bool nsKDEUtils::commandBlockUi(const nsTArray<nsCString>& command,
++ GtkWindow* parent,
++ nsTArray<nsCString>* output) {
++ return self()->internalCommand(command, parent, true, output);
++}
++
++bool nsKDEUtils::internalCommand(const nsTArray<nsCString>& command,
++ GtkWindow* parent, bool blockUi,
++ nsTArray<nsCString>* output) {
++ if (!startHelper()) return false;
++ feedCommand(command);
++ // do not store the data in 'this' but in extra structure, just in case there
++ // is reentrancy (can there be? the event loop is re-entered)
++ nsKDECommandData data;
++ data.file = replyFile;
++ data.output = output;
++ data.success = false;
++ if (blockUi) {
++ data.loop = g_main_loop_new(NULL, FALSE);
++ GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
++ if (parent && gtk_window_get_group(parent))
++ gtk_window_group_add_window(gtk_window_get_group(parent),
++ GTK_WINDOW(window));
++ gtk_widget_realize(window);
++ gtk_widget_set_sensitive(window, TRUE);
++ gtk_grab_add(window);
++ GIOChannel* channel = g_io_channel_unix_new(fileno(data.file));
++ g_io_add_watch(channel,
++ static_cast<GIOCondition>(G_IO_IN | G_IO_ERR | G_IO_HUP),
++ kdeReadFunc, &data);
++ g_io_channel_unref(channel);
++ g_main_loop_run(data.loop);
++ g_main_loop_unref(data.loop);
++ gtk_grab_remove(window);
++ gtk_widget_destroy(window);
++ } else {
++ data.loop = NULL;
++ while (kdeReadFunc(NULL, static_cast<GIOCondition>(0), &data))
++ ;
++ }
++ return data.success;
++}
++
++bool nsKDEUtils::startHelper() {
++ if (helperRunning) return true;
++ if (helperFailed) return false;
++ helperFailed = true;
++ int fdcommand[2];
++ int fdreply[2];
++ if (pipe(fdcommand) < 0) return false;
++ if (pipe(fdreply) < 0) {
++ close(fdcommand[0]);
++ close(fdcommand[1]);
++ return false;
++ }
++ char* args[2] = {const_cast<char*>(KMOZILLAHELPER), NULL};
++ switch (fork()) {
++ case -1: {
++ close(fdcommand[0]);
++ close(fdcommand[1]);
++ close(fdreply[0]);
++ close(fdreply[1]);
++ return false;
++ }
++ case 0: // child
++ {
++ if (dup2(fdcommand[0], STDIN_FILENO) < 0) _exit(1);
++ if (dup2(fdreply[1], STDOUT_FILENO) < 0) _exit(1);
++ int maxfd = 1024; // close all other fds
++ struct rlimit rl;
++ if (getrlimit(RLIMIT_NOFILE, &rl) == 0) maxfd = rl.rlim_max;
++ for (int i = 3; i < maxfd; ++i) close(i);
++#ifdef DEBUG_KDE
++ execvp(KMOZILLAHELPER, args);
++#else
++ execv(KMOZILLAHELPER, args);
++#endif
++ _exit(1); // failed
++ }
++ default: // parent
++ {
++ commandFile = fdopen(fdcommand[1], "w");
++ replyFile = fdopen(fdreply[0], "r");
++ close(fdcommand[0]);
++ close(fdreply[1]);
++ if (commandFile == NULL || replyFile == NULL) {
++ closeHelper();
++ return false;
++ }
++ // ok, helper ready, getKdeRunning() will check if it works
++ }
++ }
++ helperFailed = false;
++ helperRunning = true;
++ return true;
++}
++
++void nsKDEUtils::closeHelper() {
++ if (commandFile != NULL)
++ fclose(commandFile); // this will also make the helper quit
++ if (replyFile != NULL) fclose(replyFile);
++ helperRunning = false;
++}
++
++void nsKDEUtils::feedCommand(const nsTArray<nsCString>& command) {
++ for (int i = 0; i < command.Length(); ++i) {
++ nsCString line = command[i];
++ line.ReplaceSubstring("\\",
++ "\\"
++ "\\"); // \ -> \\ , i.e. escape
++ line.ReplaceSubstring("\n", "\\n");
++#ifdef DEBUG_KDE
++ fprintf(stderr, "COMM: %s\n", line.get());
++#endif
++ fputs(line.get(), commandFile);
++ fputs("\n", commandFile);
++ }
++ fputs("\\E\n",
++ commandFile); // done as \E, so it cannot happen in normal data
++ fflush(commandFile);
++}
+diff --git a/toolkit/xre/nsKDEUtils.h b/toolkit/xre/nsKDEUtils.h
+new file mode 100644
+index 0000000000000000000000000000000000000000..7fa6eb8e83b32c8e2c62a0035d253e06e135e3d2
+--- /dev/null
++++ b/toolkit/xre/nsKDEUtils.h
+@@ -0,0 +1,53 @@
++/* -*- 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 nsKDEUtils_h__
++#define nsKDEUtils_h__
++
++#include "nsString.h"
++#include "nsTArray.h"
++#include <stdio.h>
++
++typedef struct _GtkWindow GtkWindow;
++
++class nsIArray;
++
++class NS_EXPORT nsKDEUtils {
++ public:
++ /* Returns true if running inside a KDE session (regardless of whether there
++ is KDE support available for Firefox). This should be used e.g. when
++ determining dialog button order but not for code that requires the KDE
++ support. */
++ static bool kdeSession();
++ /* Returns true if running inside a KDE session and KDE support is available
++ for Firefox. This should be used everywhere where the external helper is
++ needed. */
++ static bool kdeSupport();
++ /* Executes the given helper command, returns true if helper returned success.
++ */
++ static bool command(const nsTArray<nsCString>& command,
++ nsTArray<nsCString>* output = NULL);
++ static bool command(nsIArray* command, nsIArray** output = NULL);
++ /* Like command(), but additionally blocks the parent widget like if there was
++ a modal dialog shown and enters the event loop (i.e. there are still paint
++ updates, this is for commands that take long). */
++ static bool commandBlockUi(const nsTArray<nsCString>& command,
++ GtkWindow* parent,
++ nsTArray<nsCString>* output = NULL);
++
++ private:
++ nsKDEUtils();
++ ~nsKDEUtils();
++ static nsKDEUtils* self();
++ bool startHelper();
++ void closeHelper();
++ void feedCommand(const nsTArray<nsCString>& command);
++ bool internalCommand(const nsTArray<nsCString>& command, GtkWindow* parent,
++ bool isParent, nsTArray<nsCString>* output);
++ FILE* commandFile;
++ FILE* replyFile;
++};
++
++#endif // nsKDEUtils
+diff --git a/uriloader/exthandler/HandlerServiceParent.cpp b/uriloader/exthandler/HandlerServiceParent.cpp
+index ab77657dd5f378af0955c43ef958a8abea620134..18b4d85560699bbc3c69b82ee91dfb5cbe700e7b 100644
+--- a/uriloader/exthandler/HandlerServiceParent.cpp
++++ b/uriloader/exthandler/HandlerServiceParent.cpp
+@@ -18,7 +18,7 @@
+ #include "nsComponentManagerUtils.h"
+ #include "nsServiceManagerUtils.h"
+ #ifdef MOZ_WIDGET_GTK
+-# include "unix/nsGNOMERegistry.h"
++# include "unix/nsCommonRegistry.h"
+ #endif
+
+ using mozilla::dom::ContentHandlerService;
+@@ -310,8 +310,8 @@ mozilla::ipc::IPCResult HandlerServiceParent::RecvExistsForProtocolOS(
+ }
+ #ifdef MOZ_WIDGET_GTK
+ // Check the GNOME registry for a protocol handler
+- *aHandlerExists =
+- nsGNOMERegistry::HandlerExists(PromiseFlatCString(aProtocolScheme).get());
++ *aHandlerExists = nsCommonRegistry::HandlerExists(
++ PromiseFlatCString(aProtocolScheme).get());
+ #else
+ *aHandlerExists = false;
+ #endif
+diff --git a/uriloader/exthandler/moz.build b/uriloader/exthandler/moz.build
+index 0fb126a7f3f7a45d53e6fb81aef74147c711cb77..8cc0006f3045e14e83fd51926ac7856eacbe7357 100644
+--- a/uriloader/exthandler/moz.build
++++ b/uriloader/exthandler/moz.build
+@@ -86,7 +86,9 @@ else:
+
+ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ UNIFIED_SOURCES += [
++ "unix/nsCommonRegistry.cpp",
+ "unix/nsGNOMERegistry.cpp",
++ "unix/nsKDERegistry.cpp",
+ "unix/nsMIMEInfoUnix.cpp",
+ ]
+ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+@@ -134,6 +136,7 @@ LOCAL_INCLUDES += [
+ "/dom/ipc",
+ "/netwerk/base",
+ "/netwerk/protocol/http",
++ "/toolkit/xre",
+ ]
+
+ if CONFIG["MOZ_ENABLE_DBUS"]:
+diff --git a/uriloader/exthandler/unix/nsCommonRegistry.cpp b/uriloader/exthandler/unix/nsCommonRegistry.cpp
+new file mode 100644
+index 0000000000000000000000000000000000000000..3371a756e2c240bfe5fe31ef0ee9c393368dab60
+--- /dev/null
++++ b/uriloader/exthandler/unix/nsCommonRegistry.cpp
+@@ -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/. */
++
++#include "nsCommonRegistry.h"
++
++#include "nsGNOMERegistry.h"
++#include "nsKDERegistry.h"
++#include "nsString.h"
++#include "nsKDEUtils.h"
++
++/* static */ bool nsCommonRegistry::HandlerExists(const char* aProtocolScheme) {
++ if (nsKDEUtils::kdeSupport())
++ return nsKDERegistry::HandlerExists(aProtocolScheme);
++ return nsGNOMERegistry::HandlerExists(aProtocolScheme);
++}
++
++/* static */ nsresult nsCommonRegistry::LoadURL(nsIURI* aURL) {
++ if (nsKDEUtils::kdeSupport()) return nsKDERegistry::LoadURL(aURL);
++ return nsGNOMERegistry::LoadURL(aURL);
++}
++
++/* static */ void nsCommonRegistry::GetAppDescForScheme(
++ const nsACString& aScheme, nsAString& aDesc) {
++ if (nsKDEUtils::kdeSupport())
++ return nsKDERegistry::GetAppDescForScheme(aScheme, aDesc);
++ return nsGNOMERegistry::GetAppDescForScheme(aScheme, aDesc);
++}
++
++/* static */ already_AddRefed<nsMIMEInfoBase>
++nsCommonRegistry::GetFromExtension(const nsACString& aFileExt) {
++ if (nsKDEUtils::kdeSupport())
++ return nsKDERegistry::GetFromExtension(aFileExt);
++ return nsGNOMERegistry::GetFromExtension(aFileExt);
++}
++
++/* static */ already_AddRefed<nsMIMEInfoBase> nsCommonRegistry::GetFromType(
++ const nsACString& aMIMEType) {
++ if (nsKDEUtils::kdeSupport()) return nsKDERegistry::GetFromType(aMIMEType);
++ return nsGNOMERegistry::GetFromType(aMIMEType);
++}
+diff --git a/uriloader/exthandler/unix/nsCommonRegistry.h b/uriloader/exthandler/unix/nsCommonRegistry.h
+new file mode 100644
+index 0000000000000000000000000000000000000000..075413e2fbb165862956c7753a750bfdfb5d389b
+--- /dev/null
++++ b/uriloader/exthandler/unix/nsCommonRegistry.h
+@@ -0,0 +1,28 @@
++/* 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 nsCommonRegistry_h__
++#define nsCommonRegistry_h__
++
++#include "nsIURI.h"
++#include "nsCOMPtr.h"
++
++class nsMIMEInfoBase;
++
++class nsCommonRegistry {
++ public:
++ static bool HandlerExists(const char* aProtocolScheme);
++
++ static nsresult LoadURL(nsIURI* aURL);
++
++ static void GetAppDescForScheme(const nsACString& aScheme, nsAString& aDesc);
++
++ static already_AddRefed<nsMIMEInfoBase> GetFromExtension(
++ const nsACString& aFileExt);
++
++ static already_AddRefed<nsMIMEInfoBase> GetFromType(
++ const nsACString& aMIMEType);
++};
++
++#endif
+diff --git a/uriloader/exthandler/unix/nsKDERegistry.cpp b/uriloader/exthandler/unix/nsKDERegistry.cpp
+new file mode 100644
+index 0000000000000000000000000000000000000000..082035566f0b82c14f866c2fbed34c0884f27d34
+--- /dev/null
++++ b/uriloader/exthandler/unix/nsKDERegistry.cpp
+@@ -0,0 +1,75 @@
++/* -*- 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 "mozilla/StaticPrefs_browser.h"
++#include "nsKDERegistry.h"
++#include "prlink.h"
++#include "prmem.h"
++#include "nsString.h"
++#include "nsMIMEInfoUnix.h"
++#include "nsKDEUtils.h"
++
++/* static */ bool nsKDERegistry::HandlerExists(const char* aProtocolScheme) {
++ nsTArray<nsCString> command;
++ command.AppendElement("HANDLEREXISTS"_ns);
++ command.AppendElement(nsAutoCString(aProtocolScheme));
++ return nsKDEUtils::command(command);
++}
++
++/* static */ nsresult nsKDERegistry::LoadURL(nsIURI* aURL) {
++ nsTArray<nsCString> command;
++ command.AppendElement("OPEN"_ns);
++ nsCString url;
++ aURL->GetSpec(url);
++ command.AppendElement(url);
++ bool rv = nsKDEUtils::command(command);
++ if (!rv) return NS_ERROR_FAILURE;
++
++ return NS_OK;
++}
++
++/* static */ void nsKDERegistry::GetAppDescForScheme(const nsACString& aScheme,
++ nsAString& aDesc) {
++ nsTArray<nsCString> command;
++ command.AppendElement("GETAPPDESCFORSCHEME"_ns);
++ command.AppendElement(aScheme);
++ nsTArray<nsCString> output;
++ if (nsKDEUtils::command(command, &output) && output.Length() == 1)
++ CopyUTF8toUTF16(output[0], aDesc);
++}
++
++/* static */ already_AddRefed<nsMIMEInfoBase> nsKDERegistry::GetFromExtension(
++ const nsACString& aFileExt) {
++ NS_ASSERTION(aFileExt[0] != '.', "aFileExt shouldn't start with a dot");
++ nsTArray<nsCString> command;
++ command.AppendElement("GETFROMEXTENSION"_ns);
++ command.AppendElement(aFileExt);
++ return GetFromHelper(command);
++}
++
++/* static */ already_AddRefed<nsMIMEInfoBase> nsKDERegistry::GetFromType(
++ const nsACString& aMIMEType) {
++ nsTArray<nsCString> command;
++ command.AppendElement("GETFROMTYPE"_ns);
++ command.AppendElement(aMIMEType);
++ return GetFromHelper(command);
++}
++
++/* static */ already_AddRefed<nsMIMEInfoBase> nsKDERegistry::GetFromHelper(
++ const nsTArray<nsCString>& command) {
++ nsTArray<nsCString> output;
++ if (nsKDEUtils::command(command, &output) && output.Length() == 3) {
++ nsCString mimetype = output[0];
++ RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(mimetype);
++ NS_ENSURE_TRUE(mimeInfo, nullptr);
++ nsCString description = output[1];
++ mimeInfo->SetDescription(NS_ConvertUTF8toUTF16(description));
++ nsCString handlerAppName = output[2];
++ mimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
++ mimeInfo->SetDefaultDescription(NS_ConvertUTF8toUTF16(handlerAppName));
++ return mimeInfo.forget();
++ }
++ return nullptr;
++}
+diff --git a/uriloader/exthandler/unix/nsKDERegistry.h b/uriloader/exthandler/unix/nsKDERegistry.h
+new file mode 100644
+index 0000000000000000000000000000000000000000..c6a41b331b2b5ead6142171f08d8b8a7872ca516
+--- /dev/null
++++ b/uriloader/exthandler/unix/nsKDERegistry.h
+@@ -0,0 +1,35 @@
++/* 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 nsKDERegistry_h__
++#define nsKDERegistry_h__
++
++#include "nsIURI.h"
++#include "nsCOMPtr.h"
++#include "nsTArray.h"
++
++class nsMIMEInfoBase;
++// class nsAutoCString;
++// class nsCString;
++
++class nsKDERegistry {
++ public:
++ static bool HandlerExists(const char* aProtocolScheme);
++
++ static nsresult LoadURL(nsIURI* aURL);
++
++ static void GetAppDescForScheme(const nsACString& aScheme, nsAString& aDesc);
++
++ static already_AddRefed<nsMIMEInfoBase> GetFromExtension(
++ const nsACString& aFileExt);
++
++ static already_AddRefed<nsMIMEInfoBase> GetFromType(
++ const nsACString& aMIMEType);
++
++ private:
++ static already_AddRefed<nsMIMEInfoBase> GetFromHelper(
++ const nsTArray<nsCString>& command);
++};
++
++#endif // nsKDERegistry_h__
+diff --git a/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
+index 330c4411597f1a19105601e256a2c3bc71c61780..c96c1f3ca5a05c3b6bce321d7a975aa040865fa8 100644
+--- a/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
++++ b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
+@@ -5,16 +5,19 @@
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ #include "nsMIMEInfoUnix.h"
+-#include "nsGNOMERegistry.h"
++#include "nsCommonRegistry.h"
+ #include "nsIGIOService.h"
+ #include "nsNetCID.h"
+ #include "nsIIOService.h"
+ #ifdef MOZ_ENABLE_DBUS
+ # include "nsDBusHandlerApp.h"
+ #endif
++#if defined(XP_UNIX) && !defined(XP_MACOSX)
++# include "nsKDEUtils.h"
++#endif
+
+ nsresult nsMIMEInfoUnix::LoadUriInternal(nsIURI* aURI) {
+- return nsGNOMERegistry::LoadURL(aURI);
++ return nsCommonRegistry::LoadURL(aURI);
+ }
+
+ NS_IMETHODIMP
+@@ -29,15 +32,15 @@ nsMIMEInfoUnix::GetHasDefaultHandler(bool* _retval) {
+ *_retval = false;
+
+ if (mClass == eProtocolInfo) {
+- *_retval = nsGNOMERegistry::HandlerExists(mSchemeOrType.get());
++ *_retval = nsCommonRegistry::HandlerExists(mSchemeOrType.get());
+ } else {
+ RefPtr<nsMIMEInfoBase> mimeInfo =
+- nsGNOMERegistry::GetFromType(mSchemeOrType);
++ nsCommonRegistry::GetFromType(mSchemeOrType);
+ if (!mimeInfo) {
+ nsAutoCString ext;
+ nsresult rv = GetPrimaryExtension(ext);
+ if (NS_SUCCEEDED(rv)) {
+- mimeInfo = nsGNOMERegistry::GetFromExtension(ext);
++ mimeInfo = nsCommonRegistry::GetFromExtension(ext);
+ }
+ }
+ if (mimeInfo) *_retval = true;
+@@ -59,6 +62,21 @@ nsresult nsMIMEInfoUnix::LaunchDefaultWithFile(nsIFile* aFile) {
+ nsAutoCString nativePath;
+ aFile->GetNativePath(nativePath);
+
++ if (nsKDEUtils::kdeSupport()) {
++ bool supports;
++ if (NS_SUCCEEDED(GetHasDefaultHandler(&supports)) && supports) {
++ nsTArray<nsCString> command;
++ command.AppendElement("OPEN"_ns);
++ command.AppendElement(nativePath);
++ command.AppendElement("MIMETYPE"_ns);
++ command.AppendElement(mSchemeOrType);
++ if (nsKDEUtils::command(command)) return NS_OK;
++ }
++ if (!GetDefaultApplication()) return NS_ERROR_FILE_NOT_FOUND;
++
++ return LaunchWithIProcess(GetDefaultApplication(), nativePath);
++ }
++
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return NS_ERROR_FAILURE;
+diff --git a/uriloader/exthandler/unix/nsOSHelperAppService.cpp b/uriloader/exthandler/unix/nsOSHelperAppService.cpp
+index 7f6eaa46f2ee0d5155b83bfb07d8040584935772..f7627e790c47e1ae007b072b4bb47b18de1ae417 100644
+--- a/uriloader/exthandler/unix/nsOSHelperAppService.cpp
++++ b/uriloader/exthandler/unix/nsOSHelperAppService.cpp
+@@ -10,7 +10,7 @@
+ #include "nsOSHelperAppService.h"
+ #include "nsMIMEInfoUnix.h"
+ #ifdef MOZ_WIDGET_GTK
+-# include "nsGNOMERegistry.h"
++# include "nsCommonRegistry.h"
+ # ifdef MOZ_BUILD_APP_IS_BROWSER
+ # include "nsIToolkitShellService.h"
+ # include "nsIGNOMEShellService.h"
+@@ -1106,7 +1106,7 @@ nsresult nsOSHelperAppService::OSProtocolHandlerExists(
+ if (!XRE_IsContentProcess()) {
+ #ifdef MOZ_WIDGET_GTK
+ // Check the GNOME registry for a protocol handler
+- *aHandlerExists = nsGNOMERegistry::HandlerExists(aProtocolScheme);
++ *aHandlerExists = nsCommonRegistry::HandlerExists(aProtocolScheme);
+ #else
+ *aHandlerExists = false;
+ #endif
+@@ -1126,7 +1126,7 @@ nsresult nsOSHelperAppService::OSProtocolHandlerExists(
+ NS_IMETHODIMP nsOSHelperAppService::GetApplicationDescription(
+ const nsACString& aScheme, nsAString& _retval) {
+ #ifdef MOZ_WIDGET_GTK
+- nsGNOMERegistry::GetAppDescForScheme(aScheme, _retval);
++ nsCommonRegistry::GetAppDescForScheme(aScheme, _retval);
+ return _retval.IsEmpty() ? NS_ERROR_NOT_AVAILABLE : NS_OK;
+ #else
+ return NS_ERROR_NOT_AVAILABLE;
+@@ -1231,7 +1231,7 @@ already_AddRefed<nsMIMEInfoBase> nsOSHelperAppService::GetFromExtension(
+ #ifdef MOZ_WIDGET_GTK
+ LOG("Looking in GNOME registry\n");
+ RefPtr<nsMIMEInfoBase> gnomeInfo =
+- nsGNOMERegistry::GetFromExtension(aFileExt);
++ nsCommonRegistry::GetFromExtension(aFileExt);
+ if (gnomeInfo) {
+ LOG("Got MIMEInfo from GNOME registry\n");
+ return gnomeInfo.forget();
+@@ -1344,7 +1344,7 @@ already_AddRefed<nsMIMEInfoBase> nsOSHelperAppService::GetFromType(
+
+ #ifdef MOZ_WIDGET_GTK
+ if (handler.IsEmpty()) {
+- RefPtr<nsMIMEInfoBase> gnomeInfo = nsGNOMERegistry::GetFromType(aMIMEType);
++ RefPtr<nsMIMEInfoBase> gnomeInfo = nsCommonRegistry::GetFromType(aMIMEType);
+ if (gnomeInfo) {
+ LOG("Got MIMEInfo from GNOME registry without extensions; setting them "
+ "to %s\n",
+diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build
+index c6a765df9e5a4c95f77e9ee1b4ebbf9913a81e15..6e9028169ac594a24f90a4f58dc493c8332c6bf8 100644
+--- a/widget/gtk/moz.build
++++ b/widget/gtk/moz.build
+@@ -161,6 +161,7 @@ LOCAL_INCLUDES += [
+ "/layout/xul",
+ "/other-licenses/atk-1.0",
+ "/third_party/cups/include",
++ "/toolkit/xre",
+ "/widget",
+ "/widget/headless",
+ "/widget/x11",
+diff --git a/widget/gtk/nsFilePicker.cpp b/widget/gtk/nsFilePicker.cpp
+index 22d0f46b9563734c7afb4292417124f3cd171a12..b2a68711eb344d5ac41a7133751b2c19d574f532 100644
+--- a/widget/gtk/nsFilePicker.cpp
++++ b/widget/gtk/nsFilePicker.cpp
+@@ -5,6 +5,7 @@
+
+ #include <dlfcn.h>
+ #include <gtk/gtk.h>
++#include <gdk/gdkx.h>
+ #include <sys/types.h>
+ #include <sys/stat.h>
+ #include <unistd.h>
+@@ -28,6 +29,8 @@
+ #include "WidgetUtilsGtk.h"
+
+ #include "nsFilePicker.h"
++#include "nsKDEUtils.h"
++#include "nsURLHelper.h"
+
+ #undef LOG
+ #ifdef MOZ_LOGGING
+@@ -242,7 +245,8 @@ NS_IMETHODIMP
+ nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) {
+ if (aFilter.EqualsLiteral("..apps")) {
+ // No platform specific thing we can do here, really....
+- return NS_OK;
++ // Unless it's KDE.
++ if (mMode != modeOpen || !nsKDEUtils::kdeSupport()) return NS_OK;
+ }
+
+ nsAutoCString filter, name;
+@@ -352,6 +356,31 @@ nsFilePicker::Open(nsIFilePickerShownCallback* aCallback) {
+ // Can't show two dialogs concurrently with the same filepicker
+ if (mRunning) return NS_ERROR_NOT_AVAILABLE;
+
++ // KDE file picker is not handled via callback
++ if (nsKDEUtils::kdeSupport()) {
++ mCallback = aCallback;
++ mRunning = true;
++ NS_ADDREF_THIS();
++ g_idle_add(
++ [](gpointer data) -> gboolean {
++ nsFilePicker* queuedPicker = (nsFilePicker*)data;
++ nsIFilePicker::ResultCode result;
++ queuedPicker->kdeFileDialog(&result);
++ if (queuedPicker->mCallback) {
++ queuedPicker->mCallback->Done(result);
++ queuedPicker->mCallback = nullptr;
++ } else {
++ queuedPicker->mResult = result;
++ }
++ queuedPicker->mRunning = false;
++ NS_RELEASE(queuedPicker);
++ return G_SOURCE_REMOVE;
++ },
++ this);
++
++ return NS_OK;
++ }
++
+ NS_ConvertUTF16toUTF8 title(mTitle);
+
+ GtkWindow* parent_widget =
+@@ -633,6 +662,205 @@ void nsFilePicker::Done(void* file_chooser, gint response) {
+ NS_RELEASE_THIS();
+ }
+
++nsCString nsFilePicker::kdeMakeFilter(int index) {
++ nsCString buf = mFilters[index];
++ for (PRUint32 i = 0; i < buf.Length(); ++i)
++ if (buf[i] == ';') // KDE separates just using spaces
++ buf.SetCharAt(' ', i);
++ if (!mFilterNames[index].IsEmpty()) {
++ buf += "|";
++ buf += mFilterNames[index].get();
++ }
++ return buf;
++}
++
++static PRInt32 windowToXid(nsIWidget* widget) {
++ GtkWindow* parent_widget =
++ GTK_WINDOW(widget->GetNativeData(NS_NATIVE_SHELLWIDGET));
++ GdkWindow* gdk_window =
++ gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(parent_widget)));
++ return GDK_WINDOW_XID(gdk_window);
++}
++
++NS_IMETHODIMP nsFilePicker::kdeFileDialog(nsIFilePicker::ResultCode* aReturn) {
++ NS_ENSURE_ARG_POINTER(aReturn);
++
++ if (mMode == modeOpen && mFilters.Length() == 1 &&
++ mFilters[0].EqualsLiteral("..apps"))
++ return kdeAppsDialog(aReturn);
++
++ nsCString title;
++ title.Adopt(ToNewUTF8String(mTitle));
++
++ const char* arg = NULL;
++ if (mAllowURLs) {
++ switch (mMode) {
++ case nsIFilePicker::modeOpen:
++ case nsIFilePicker::modeOpenMultiple:
++ arg = "GETOPENURL";
++ break;
++ case nsIFilePicker::modeSave:
++ arg = "GETSAVEURL";
++ break;
++ case nsIFilePicker::modeGetFolder:
++ arg = "GETDIRECTORYURL";
++ break;
++ }
++ } else {
++ switch (mMode) {
++ case nsIFilePicker::modeOpen:
++ case nsIFilePicker::modeOpenMultiple:
++ arg = "GETOPENFILENAME";
++ break;
++ case nsIFilePicker::modeSave:
++ arg = "GETSAVEFILENAME";
++ break;
++ case nsIFilePicker::modeGetFolder:
++ arg = "GETDIRECTORYFILENAME";
++ break;
++ }
++ }
++
++ nsAutoCString directory;
++ if (mDisplayDirectory) {
++ mDisplayDirectory->GetNativePath(directory);
++ } else if (mPrevDisplayDirectory) {
++ mPrevDisplayDirectory->GetNativePath(directory);
++ }
++
++ nsAutoCString startdir;
++ if (!directory.IsEmpty()) {
++ startdir = directory;
++ }
++ if (mMode == nsIFilePicker::modeSave) {
++ if (!startdir.IsEmpty()) {
++ startdir += "/";
++ startdir += ToNewUTF8String(mDefault);
++ } else
++ startdir = ToNewUTF8String(mDefault);
++ }
++
++ nsAutoCString filters;
++ PRInt32 count = mFilters.Length();
++ if (count == 0) // just in case
++ filters = "*";
++ else {
++ filters = kdeMakeFilter(0);
++ for (PRInt32 i = 1; i < count; ++i) {
++ filters += "\n";
++ filters += kdeMakeFilter(i);
++ }
++ }
++
++ nsTArray<nsCString> command;
++ command.AppendElement(nsAutoCString(arg));
++ command.AppendElement(startdir);
++ if (mMode != nsIFilePicker::modeGetFolder) {
++ command.AppendElement(filters);
++ nsAutoCString selected;
++ selected.AppendInt(mSelectedType);
++ command.AppendElement(selected);
++ }
++ command.AppendElement(title);
++ if (mMode == nsIFilePicker::modeOpenMultiple)
++ command.AppendElement("MULTIPLE"_ns);
++ if (PRInt32 xid = windowToXid(mParentWidget)) {
++ command.AppendElement("PARENT"_ns);
++ nsAutoCString parent;
++ parent.AppendInt(xid);
++ command.AppendElement(parent);
++ }
++
++ nsTArray<nsCString> output;
++ if (nsKDEUtils::commandBlockUi(
++ command,
++ GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)),
++ &output)) {
++ *aReturn = nsIFilePicker::returnOK;
++ mFiles.Clear();
++ if (mMode != nsIFilePicker::modeGetFolder) {
++ mSelectedType = atoi(output[0].get());
++ output.RemoveElementAt(0);
++ }
++ if (mMode == nsIFilePicker::modeOpenMultiple) {
++ mFileURL.Truncate();
++ PRUint32 count = output.Length();
++ for (PRUint32 i = 0; i < count; ++i) {
++ nsCOMPtr<nsIFile> localfile;
++ nsresult rv = NS_NewNativeLocalFile(output[i], PR_FALSE,
++ getter_AddRefs(localfile));
++ if (NS_SUCCEEDED(rv)) mFiles.AppendObject(localfile);
++ }
++ } else {
++ if (output.Length() == 0)
++ mFileURL = nsCString();
++ else if (mAllowURLs)
++ mFileURL = output[0];
++ else // GetFile() actually requires it to be url even for local files :-/
++ {
++ nsCOMPtr<nsIFile> localfile;
++ nsresult rv = NS_NewNativeLocalFile(output[0], PR_FALSE,
++ getter_AddRefs(localfile));
++ if (NS_SUCCEEDED(rv))
++ rv = net_GetURLSpecFromActualFile(localfile, mFileURL);
++ }
++ }
++ // Remember last used directory.
++ nsCOMPtr<nsIFile> file;
++ GetFile(getter_AddRefs(file));
++ if (file) {
++ nsCOMPtr<nsIFile> dir;
++ file->GetParent(getter_AddRefs(dir));
++ nsCOMPtr<nsIFile> localDir(dir);
++ if (localDir) {
++ localDir.swap(mPrevDisplayDirectory);
++ }
++ }
++ if (mMode == nsIFilePicker::modeSave) {
++ nsCOMPtr<nsIFile> file;
++ GetFile(getter_AddRefs(file));
++ if (file) {
++ bool exists = false;
++ file->Exists(&exists);
++ if (exists) // TODO do overwrite check in the helper app
++ *aReturn = nsIFilePicker::returnReplace;
++ }
++ }
++ } else {
++ *aReturn = nsIFilePicker::returnCancel;
++ }
++ return NS_OK;
++}
++
++NS_IMETHODIMP nsFilePicker::kdeAppsDialog(nsIFilePicker::ResultCode* aReturn) {
++ NS_ENSURE_ARG_POINTER(aReturn);
++
++ nsCString title;
++ title.Adopt(ToNewUTF8String(mTitle));
++
++ nsTArray<nsCString> command;
++ command.AppendElement("APPSDIALOG"_ns);
++ command.AppendElement(title);
++ if (PRInt32 xid = windowToXid(mParentWidget)) {
++ command.AppendElement("PARENT"_ns);
++ nsAutoCString parent;
++ parent.AppendInt(xid);
++ command.AppendElement(parent);
++ }
++
++ nsTArray<nsCString> output;
++ if (nsKDEUtils::commandBlockUi(
++ command,
++ GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)),
++ &output)) {
++ *aReturn = nsIFilePicker::returnOK;
++ mFileURL = output.Length() > 0 ? output[0] : nsCString();
++ } else {
++ *aReturn = nsIFilePicker::returnCancel;
++ }
++ return NS_OK;
++}
++
+ // All below functions available as of GTK 3.20+
+ void* nsFilePicker::GtkFileChooserNew(const gchar* title, GtkWindow* parent,
+ GtkFileChooserAction action,
+diff --git a/widget/gtk/nsFilePicker.h b/widget/gtk/nsFilePicker.h
+index 496df4937277d96485376176296ee836aa261ec7..a4c1862ec042f4465d53bc95f138afb87260ba07 100644
+--- a/widget/gtk/nsFilePicker.h
++++ b/widget/gtk/nsFilePicker.h
+@@ -74,6 +74,12 @@ class nsFilePicker : public nsBaseFilePicker {
+ private:
+ static nsIFile* mPrevDisplayDirectory;
+
++ bool kdeRunning();
++ bool getKdeRunning();
++ NS_IMETHODIMP kdeFileDialog(nsIFilePicker::ResultCode* aReturn);
++ NS_IMETHODIMP kdeAppsDialog(nsIFilePicker::ResultCode* aReturn);
++ nsCString kdeMakeFilter(int index);
++
+ void* GtkFileChooserNew(const gchar* title, GtkWindow* parent,
+ GtkFileChooserAction action,
+ const gchar* accept_label);
+diff --git a/xpcom/components/ManifestParser.cpp b/xpcom/components/ManifestParser.cpp
+index 88ee06d78db60a84343fd3d23c16e163aead37c3..834d6a2d353cc1bd11916de8a28f5d05a86d9031 100644
+--- a/xpcom/components/ManifestParser.cpp
++++ b/xpcom/components/ManifestParser.cpp
+@@ -43,6 +43,7 @@
+ #include "nsIScriptError.h"
+ #include "nsIXULAppInfo.h"
+ #include "nsIXULRuntime.h"
++#include "nsKDEUtils.h"
+
+ using namespace mozilla;
+
+@@ -394,6 +395,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+ constexpr auto kOs = u"os"_ns;
+ constexpr auto kOsVersion = u"osversion"_ns;
+ constexpr auto kABI = u"abi"_ns;
++ constexpr auto kDesktop = u"desktop"_ns;
+ constexpr auto kProcess = u"process"_ns;
+ #if defined(MOZ_WIDGET_ANDROID)
+ constexpr auto kTablet = u"tablet"_ns;
+@@ -453,6 +455,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+ }
+
+ nsAutoString osVersion;
++ nsAutoString desktop;
+ #if defined(XP_WIN)
+ # pragma warning(push)
+ # pragma warning(disable : 4996) // VC12+ deprecates GetVersionEx
+@@ -461,14 +464,17 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+ nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", info.dwMajorVersion,
+ info.dwMinorVersion);
+ }
++ desktop = u"win"_ns;
+ # pragma warning(pop)
+ #elif defined(MOZ_WIDGET_COCOA)
+ SInt32 majorVersion = nsCocoaFeatures::macOSVersionMajor();
+ SInt32 minorVersion = nsCocoaFeatures::macOSVersionMinor();
+ nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", majorVersion, minorVersion);
++ desktop = u"macosx"_ns);
+ #elif defined(MOZ_WIDGET_GTK)
+ nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", gtk_major_version,
+ gtk_minor_version);
++ desktop = nsKDEUtils::kdeSession() ? u"kde"_ns : u"gnome"_ns;
+ #elif defined(MOZ_WIDGET_ANDROID)
+ bool isTablet = false;
+ if (jni::IsAvailable()) {
+@@ -476,6 +482,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+ osVersion.Assign(release->ToString());
+ isTablet = java::GeckoAppShell::IsTablet();
+ }
++ desktop = u"android"_ns;
+ #endif
+
+ if (XRE_IsContentProcess()) {
+@@ -576,6 +583,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+ : eUnspecified;
+ #endif
+ int flags = 0;
++ TriState stDesktop = eUnspecified;
+
+ while ((token = nsCRT::strtok(whitespace, kWhitespace, &whitespace)) &&
+ ok) {
+@@ -585,6 +593,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+ if (CheckStringFlag(kApplication, wtoken, appID, stApp) ||
+ CheckOsFlag(kOs, wtoken, osTarget, stOs) ||
+ CheckStringFlag(kABI, wtoken, abi, stABI) ||
++ CheckStringFlag(kDesktop, wtoken, desktop, stDesktop) ||
+ CheckStringFlag(kProcess, wtoken, process, stProcess) ||
+ CheckVersionFlag(kOsVersion, wtoken, osVersion, stOsVersion) ||
+ CheckVersionFlag(kAppVersion, wtoken, appVersion, stAppVersion) ||
+@@ -644,6 +653,7 @@ void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf,
+
+ if (!ok || stApp == eBad || stAppVersion == eBad ||
+ stGeckoVersion == eBad || stOs == eBad || stOsVersion == eBad ||
++ stDesktop == eBad ||
+ #ifdef MOZ_WIDGET_ANDROID
+ stTablet == eBad ||
+ #endif
+diff --git a/xpcom/components/moz.build b/xpcom/components/moz.build
+index 95ee64e985ac34dd6a3191f1948afa6d05adcb73..9af8f80497b7390b7ca434b6ee3b86b2baf47489 100644
+--- a/xpcom/components/moz.build
++++ b/xpcom/components/moz.build
+@@ -71,6 +71,7 @@ LOCAL_INCLUDES += [
+ "/js/xpconnect/loader",
+ "/layout/build",
+ "/modules/libjar",
++ "/toolkit/xre",
+ ]
+
+ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+diff --git a/xpcom/io/nsLocalFileUnix.cpp b/xpcom/io/nsLocalFileUnix.cpp
+index 08c77360de6fdbf3dc579ea49243dbdc18f37ebc..eedd5bcf86bde3ecd795bbbcbf94799f19135323 100644
+--- a/xpcom/io/nsLocalFileUnix.cpp
++++ b/xpcom/io/nsLocalFileUnix.cpp
+@@ -51,6 +51,7 @@
+
+ #ifdef MOZ_WIDGET_GTK
+ # include "nsIGIOService.h"
++# include "nsKDEUtils.h"
+ #endif
+
+ #ifdef MOZ_WIDGET_COCOA
+@@ -2172,10 +2173,18 @@ nsLocalFile::Reveal() {
+ }
+
+ #ifdef MOZ_WIDGET_GTK
++ nsAutoCString url;
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+- if (!giovfs) {
+- return NS_ERROR_FAILURE;
++ url = mPath;
++ if (nsKDEUtils::kdeSupport()) {
++ nsTArray<nsCString> command;
++ command.AppendElement("REVEAL"_ns);
++ command.AppendElement(mPath);
++ return nsKDEUtils::command(command) ? NS_OK : NS_ERROR_FAILURE;
+ }
++
++ if (!giovfs) return NS_ERROR_FAILURE;
++
+ return giovfs->RevealFile(this);
+ #elif defined(MOZ_WIDGET_COCOA)
+ CFURLRef url;
+@@ -2197,6 +2206,13 @@ nsLocalFile::Launch() {
+ }
+
+ #ifdef MOZ_WIDGET_GTK
++ if (nsKDEUtils::kdeSupport()) {
++ nsTArray<nsCString> command;
++ command.AppendElement("OPEN"_ns);
++ command.AppendElement(mPath);
++ return nsKDEUtils::command(command) ? NS_OK : NS_ERROR_FAILURE;
++ }
++
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (!giovfs) {
+ return NS_ERROR_FAILURE;