mozilla-kde.patch
author Wolfgang Rosenauer <wr@rosenauer.org>
Wed, 25 Oct 2023 11:49:20 +0200
branchfirefox118
changeset 1196 954851a35787
parent 1191 37e065158be7
child 1199 4c520ebe1ad7
permissions -rw-r--r--
118.0.2 latest

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)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=140751
Bug: https://bugzilla.suse.com/show_bug.cgi?id=170055

EDIT: Björn Bidar: Removed handling for obsolete special files

Co-authored-by: Wolfgang Rosenauer <wolfgang@rosenauer.org>
Co-authored-by: Lubos Lunak <lunak@suse.com>
Co-authored-by: Björn Bidar <bjorn.bidar@thaodan.de>
---
 modules/libpref/Preferences.cpp               |   1 +
 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, 910 insertions(+), 36 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 94a5aa0f3f169563d570e18fc1a525994293ad99..c1cf316d05b742502f7b07da1b7f25024b09f4e8 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>
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;