Bug 1297686 - List GIO handlers in protocol handler list in handlers dialog and in preferences, r?paolo draft
authorJan Horak <jhorak@redhat.com>
Mon, 13 Nov 2017 11:49:13 +0100
changeset 697109 602b4921bfd449de698fae07540296e848c955c5
parent 696951 6371e71153c8c3d32d31c8eb92ac296f353dd860
child 740022 1112cfe2d02320a11657d550b5d95867f4b6758f
push id88895
push userbmo:jhorak@redhat.com
push dateMon, 13 Nov 2017 10:49:53 +0000
reviewerspaolo
bugs1297686
milestone59.0a1
Bug 1297686 - List GIO handlers in protocol handler list in handlers dialog and in preferences, r?paolo The nsGIOService now provides GetAppsForURIScheme which is used to append handlers for specific scheme in handler list dialog (toolkit/mozapps/handling/content/dialog.js) and also in Applications section in preferences. In case the default system handler or user added handler has same name as one of the GIO handlers, the GIO handler is not appended. The check for not adding handler is by using handler name. The nsGIOMimeApp class now implements nsIHandlerApp interface. Instead overloaded GetName methods (nsCString and nsString) we now use nsString variant everywhere. This require change of nsGNOMERegistry::GetFromType which if fact leads to code simplification. The implementation of nsGNOMEShellService::SetDefaultBrowser has been changed because implementation of CreateAppFromCommand has changed. The CreateAppFromCommand no longer tries to find the application, for that FindAppFromCommand has been introduced. MozReview-Commit-ID: KmfFWRPqV3
browser/components/preferences/in-content/main.js
browser/components/shell/nsGNOMEShellService.cpp
toolkit/mozapps/handling/content/dialog.js
toolkit/system/gnome/nsGIOService.cpp
uriloader/exthandler/nsHandlerService-json.js
uriloader/exthandler/tests/unit/test_handlerService_json.js
uriloader/exthandler/unix/nsGNOMERegistry.cpp
xpcom/system/nsIGIOService.idl
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -1696,16 +1696,19 @@ var gMainPane = {
       return this._isValidHandlerExecutable(aHandlerApp.executable);
 
     if (aHandlerApp instanceof Ci.nsIWebHandlerApp)
       return aHandlerApp.uriTemplate;
 
     if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo)
       return aHandlerApp.uri;
 
+    if (aHandlerApp instanceof Ci.nsIGIOMimeApp)
+      return aHandlerApp.command;
+
     return false;
   },
 
   _isValidHandlerExecutable(aExecutable) {
     let leafName;
     if (AppConstants.platform == "win") {
       leafName = `${AppConstants.MOZ_APP_NAME}.exe`;
     } else if (AppConstants.platform == "macosx") {
@@ -1833,16 +1836,56 @@ var gMainPane = {
 
       // Attach the handler app object to the menu item so we can use it
       // to make changes to the datastore when the user selects the item.
       menuItem.handlerApp = possibleApp;
 
       menuPopup.appendChild(menuItem);
       possibleAppMenuItems.push(menuItem);
     }
+    // Add gio handlers
+    if (Cc["@mozilla.org/gio-service;1"]) {
+      let gIOSvc = Cc["@mozilla.org/gio-service;1"].
+                   getService(Ci.nsIGIOService);
+      var gioApps = gIOSvc.getAppsForURIScheme(typeItem.type);
+      let enumerator = gioApps.enumerate();
+      let possibleHandlers = handlerInfo.possibleApplicationHandlers
+      while (enumerator.hasMoreElements()) {
+        let handler = enumerator.getNext().QueryInterface(Ci.nsIHandlerApp);
+        // OS handler share the same name, it's most likely the same app, skipping...
+        if (handler.name == handlerInfo.defaultDescription) {
+          continue;
+        }
+        // Check if the handler is already in possibleHandlers
+        let appAlreadyInHandlers = false;
+        for (let i = possibleHandlers.length - 1; i >= 0; --i) {
+          let app = possibleHandlers.queryElementAt(i, Ci.nsIHandlerApp);
+          // nsGIOMimeApp::Equals is able to compare with nsILocalHandlerApp
+          if (handler.equals(app)) {
+            appAlreadyInHandlers = true;
+            break;
+          }
+        }
+        if (!appAlreadyInHandlers) {
+          let menuItem = document.createElement("menuitem");
+          menuItem.setAttribute("action", Ci.nsIHandlerInfo.useHelperApp);
+          let label = this._prefsBundle.getFormattedString("useApp", [handler.name]);
+          menuItem.setAttribute("label", label);
+          menuItem.setAttribute("tooltiptext", label);
+          menuItem.setAttribute("image", this._getIconURLForHandlerApp(handler));
+
+          // Attach the handler app object to the menu item so we can use it
+          // to make changes to the datastore when the user selects the item.
+          menuItem.handlerApp = handler;
+
+          menuPopup.appendChild(menuItem);
+          possibleAppMenuItems.push(menuItem);
+        }
+      }
+    }
 
     // Create a menu item for the plugin.
     if (handlerInfo.pluginName) {
       var pluginMenuItem = document.createElement("menuitem");
       pluginMenuItem.setAttribute("action", kActionUsePlugin);
       let label = this._prefsBundle.getFormattedString("usePluginIn",
         [handlerInfo.pluginName,
         this._brandShortName]);
--- a/browser/components/shell/nsGNOMEShellService.cpp
+++ b/browser/components/shell/nsGNOMEShellService.cpp
@@ -289,20 +289,25 @@ nsGNOMEShellService::SetDefaultBrowser(b
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsAutoString brandShortName;
     brandBundle->GetStringFromName("brandShortName", brandShortName);
 
     // use brandShortName as the application id.
     NS_ConvertUTF16toUTF8 id(brandShortName);
     nsCOMPtr<nsIGIOMimeApp> appInfo;
-    rv = giovfs->CreateAppFromCommand(mAppPath,
-                                      id,
-                                      getter_AddRefs(appInfo));
-    NS_ENSURE_SUCCESS(rv, rv);
+    rv = giovfs->FindAppFromCommand(mAppPath, getter_AddRefs(appInfo));
+    if (NS_FAILED(rv)) {
+      // Application was not found in the list of installed applications provided
+      // by OS. Fallback to create appInfo from command and name.
+      rv = giovfs->CreateAppFromCommand(mAppPath,
+                                        id,
+                                        getter_AddRefs(appInfo));
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
 
     // set handler for the protocols
     for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) {
       if (appProtocols[i].essential || aClaimAllTypes) {
         appInfo->SetAsDefaultForURIScheme(nsDependentCString(appProtocols[i].name));
       }
     }
 
--- a/toolkit/mozapps/handling/content/dialog.js
+++ b/toolkit/mozapps/handling/content/dialog.js
@@ -128,18 +128,20 @@ var dialog = {
           // and users won't visit the handler's URL template, they'll only
           // visit URLs derived from that template (i.e. with %s in the template
           // replaced by the URL of the content being handled).
           elm.setAttribute("image", uri.prePath + "/favicon.ico");
         }
         elm.setAttribute("description", uri.prePath);
       } else if (app instanceof Ci.nsIDBusHandlerApp) {
         elm.setAttribute("description", app.method);
-      } else
+      } else if (!(app instanceof Ci.nsIGIOMimeApp)) {
+        // We support GIO application handler, but no action required there
         throw "unknown handler type";
+      }
 
       items.insertBefore(elm, this._itemChoose);
       if (preferredHandler && app == preferredHandler)
         this.selectedItem = elm;
     }
 
     if (this._handlerInfo.hasDefaultHandler) {
       let elm = document.createElement("richlistitem");
@@ -147,16 +149,49 @@ var dialog = {
       elm.id = "os-default-handler";
       elm.setAttribute("name", this._handlerInfo.defaultDescription);
 
       items.insertBefore(elm, items.firstChild);
       if (this._handlerInfo.preferredAction ==
           Ci.nsIHandlerInfo.useSystemDefault)
           this.selectedItem = elm;
     }
+
+    // Add gio handlers
+    if (Cc["@mozilla.org/gio-service;1"]) {
+      let gIOSvc = Cc["@mozilla.org/gio-service;1"]
+                     .getService(Ci.nsIGIOService);
+      var gioApps = gIOSvc.getAppsForURIScheme(this._URI.scheme);
+      let enumerator = gioApps.enumerate();
+      while (enumerator.hasMoreElements()) {
+        let handler = enumerator.getNext().QueryInterface(Ci.nsIHandlerApp);
+        // OS handler share the same name, it's most likely the same app, skipping...
+        if (handler.name == this._handlerInfo.defaultDescription) {
+          continue;
+        }
+        // Check if the handler is already in possibleHandlers
+        let appAlreadyInHandlers = false;
+        for (let i = possibleHandlers.length - 1; i >= 0; --i) {
+          let app = possibleHandlers.queryElementAt(i, Ci.nsIHandlerApp);
+          // nsGIOMimeApp::Equals is able to compare with nsILocalHandlerApp
+          if (handler.equals(app)) {
+            appAlreadyInHandlers = true;
+            break;
+          }
+        }
+        if (!appAlreadyInHandlers) {
+          let elm = document.createElement("richlistitem");
+          elm.setAttribute("type", "handler");
+          elm.setAttribute("name", handler.name);
+          elm.obj = handler;
+          items.insertBefore(elm, this._itemChoose);
+        }
+      }
+    }
+
     items.ensureSelectedElementIsVisible();
   },
 
  /**
   * Brings up a filepicker and allows a user to choose an application.
   */
   chooseApplication: function chooseApplication() {
     var bundle = document.getElementById("base-strings");
--- a/toolkit/system/gnome/nsGIOService.cpp
+++ b/toolkit/system/gnome/nsGIOService.cpp
@@ -4,52 +4,87 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsGIOService.h"
 #include "nsString.h"
 #include "nsIURI.h"
 #include "nsTArray.h"
 #include "nsIStringEnumerator.h"
 #include "nsAutoPtr.h"
+#include "nsIMIMEInfo.h"
+#include "nsComponentManagerUtils.h"
+#include "nsArray.h"
+#include "nsIFile.h"
 
 #include <gio/gio.h>
 #include <gtk/gtk.h>
 #ifdef MOZ_ENABLE_DBUS
 #include <dbus/dbus-glib.h>
 #include <dbus/dbus-glib-lowlevel.h>
 #endif
 
 
+/**
+ * Get command without any additional arguments
+ * @param aCommandWithArguments full commandline input string
+ * @param aCommand string for storing command without arguments
+ * @return NS_ERROR_FAILURE when unable to parse commandline
+ */
+static nsresult
+GetCommandFromCommandline(nsACString const& aCommandWithArguments, nsACString& aCommand) {
+  GError *error = nullptr;
+  gchar **argv = nullptr;
+  if (!g_shell_parse_argv(aCommandWithArguments.BeginReading(), nullptr, &argv, &error) ||
+      !argv[0]) {
+    g_warning("Cannot parse command with arguments: %s", error->message);
+    g_error_free(error);
+    g_strfreev(argv);
+    return NS_ERROR_FAILURE;
+  }
+  aCommand.Assign(argv[0]);
+  g_strfreev(argv);
+  return NS_OK;
+}
+
 class nsGIOMimeApp final : public nsIGIOMimeApp
 {
 public:
   NS_DECL_ISUPPORTS
+  NS_DECL_NSIHANDLERAPP
   NS_DECL_NSIGIOMIMEAPP
 
   explicit nsGIOMimeApp(GAppInfo* aApp) : mApp(aApp) {}
 
 private:
   ~nsGIOMimeApp() { g_object_unref(mApp); }
 
   GAppInfo *mApp;
 };
 
-NS_IMPL_ISUPPORTS(nsGIOMimeApp, nsIGIOMimeApp)
+NS_IMPL_ISUPPORTS(nsGIOMimeApp, nsIGIOMimeApp, nsIHandlerApp)
 
 NS_IMETHODIMP
 nsGIOMimeApp::GetId(nsACString& aId)
 {
   aId.Assign(g_app_info_get_id(mApp));
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsGIOMimeApp::GetName(nsACString& aName)
+nsGIOMimeApp::GetName(nsAString& aName)
 {
-  aName.Assign(g_app_info_get_name(mApp));
+  aName.Assign(NS_ConvertUTF8toUTF16(g_app_info_get_name(mApp)));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::SetName(const nsAString& aName)
+{
+  // We don't implement SetName because we're using mGIOMimeApp instance for
+  // obtaining application name
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsGIOMimeApp::GetCommand(nsACString& aCommand)
 {
   const char *cmd = g_app_info_get_commandline(mApp);
   if (!cmd)
@@ -79,16 +114,81 @@ nsGIOMimeApp::Launch(const nsACString& a
     g_warning("Cannot launch application: %s", error->message);
     g_error_free(error);
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsGIOMimeApp::GetDetailedDescription(nsAString& aDetailedDescription)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::SetDetailedDescription(const nsAString& aDetailedDescription)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::Equals(nsIHandlerApp* aHandlerApp, bool* _retval)
+{
+  if (!aHandlerApp)
+    return NS_ERROR_FAILURE;
+
+  // Compare with nsILocalHandlerApp instance by name
+  nsCOMPtr<nsILocalHandlerApp> localHandlerApp = do_QueryInterface(aHandlerApp);
+  if (localHandlerApp) {
+    nsAutoString theirName;
+    nsAutoString thisName;
+    GetName(thisName);
+    localHandlerApp->GetName(theirName);
+    *_retval = thisName.Equals(theirName);
+    return NS_OK;
+  }
+
+  // Compare with nsIGIOMimeApp instance by command with stripped arguments
+  nsCOMPtr<nsIGIOMimeApp> gioMimeApp = do_QueryInterface(aHandlerApp);
+  if (gioMimeApp) {
+    nsAutoCString thisCommandline, thisCommand;
+    nsresult rv = GetCommand(thisCommandline);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = GetCommandFromCommandline(thisCommandline,
+                                   thisCommand);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsAutoCString theirCommandline, theirCommand;
+    gioMimeApp->GetCommand(theirCommandline);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = GetCommandFromCommandline(theirCommandline,
+                                   theirCommand);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    *_retval = thisCommand.Equals(theirCommand);
+    return NS_OK;
+  }
+
+  // We can only compare with nsILocalHandlerApp and nsGIOMimeApp
+  *_retval = false;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGIOMimeApp::LaunchWithURI(nsIURI* aUri, nsIInterfaceRequestor* aRequestor)
+{
+  nsCString uri_string;
+  aUri->GetSpec(uri_string);
+  return Launch(uri_string);
+}
+
 class GIOUTF8StringEnumerator final : public nsIUTF8StringEnumerator
 {
   ~GIOUTF8StringEnumerator() = default;
 
 public:
   GIOUTF8StringEnumerator() : mIndex(0) { }
 
   NS_DECL_ISUPPORTS
@@ -278,16 +378,43 @@ nsGIOService::GetAppForURIScheme(const n
     NS_ADDREF(*aApp = mozApp);
   } else {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsGIOService::GetAppsForURIScheme(const nsACString& aURIScheme,
+                                  nsIMutableArray** aResult)
+{
+  nsCOMPtr<nsIMutableArray> handlersArray =
+    do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+  nsAutoCString contentType("x-scheme-handler/");
+  contentType.Append(aURIScheme);
+
+  GList* appInfoList = g_app_info_get_all_for_type(contentType.get());
+  // g_app_info_get_all_for_type returns NULL when no appinfo is found
+  // or error occurs (contentType is NULL). We are fine with empty app list
+  // and we're sure that contentType is not NULL, so we won't return failure.
+  if (appInfoList) {
+    GList* appInfo = appInfoList;
+    while (appInfo) {
+      nsCOMPtr<nsIGIOMimeApp> mimeApp = new nsGIOMimeApp(G_APP_INFO(appInfo->data));
+      handlersArray->AppendElement(mimeApp);
+      appInfo = appInfo->next;
+    }
+    g_list_free(appInfoList);
+  }
+  NS_ADDREF(*aResult = handlersArray);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsGIOService::GetAppForMimeType(const nsACString& aMimeType,
                                 nsIGIOMimeApp**   aApp)
 {
   *aApp = nullptr;
   char *content_type =
     g_content_type_from_mime_type(PromiseFlatCString(aMimeType).get());
   if (!content_type)
     return NS_ERROR_FAILURE;
@@ -412,64 +539,101 @@ nsGIOService::OrgFreedesktopFileManager1
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return NS_OK;
 #endif
 }
 
 /**
- * Create or find already existing application info for specified command
- * and application name.
- * @param cmd command to execute
- * @param appName application name
- * @param appInfo location where created GAppInfo is stored
- * @return NS_OK when object is created, NS_ERROR_FAILURE otherwise.
+ * Find GIO Mime App from given commandline.
+ * This is different from CreateAppFromCommand because instead of creating the
+ * GIO Mime App in case it's not found in the GIO application list, the method
+ * returns error.
+ * @param aCmd command with parameters used to start the application
+ * @return NS_OK when application is found, NS_ERROR_NOT_AVAILABLE otherwise
  */
 NS_IMETHODIMP
-nsGIOService::CreateAppFromCommand(nsACString const& cmd,
-                                   nsACString const& appName,
-                                   nsIGIOMimeApp**   appInfo)
+nsGIOService::FindAppFromCommand(nsACString const& aCmd,
+                                 nsIGIOMimeApp** aAppInfo)
 {
-  GError *error = nullptr;
-  *appInfo = nullptr;
-
   GAppInfo *app_info = nullptr, *app_info_from_list = nullptr;
   GList *apps = g_app_info_get_all();
   GList *apps_p = apps;
 
   // Try to find relevant and existing GAppInfo in all installed application
   // We do this by comparing each GAppInfo's executable with out own
   while (apps_p) {
     app_info_from_list = (GAppInfo*) apps_p->data;
     if (!app_info) {
       // If the executable is not absolute, get it's full path
       char *executable = g_find_program_in_path(g_app_info_get_executable(app_info_from_list));
 
-      if (executable && strcmp(executable, PromiseFlatCString(cmd).get()) == 0) {
+      if (executable && strcmp(executable, PromiseFlatCString(aCmd).get()) == 0) {
         g_object_ref (app_info_from_list);
         app_info = app_info_from_list;
       }
       g_free(executable);
     }
 
     g_object_unref(app_info_from_list);
     apps_p = apps_p->next;
   }
   g_list_free(apps);
-
-  if (!app_info) {
-    app_info = g_app_info_create_from_commandline(PromiseFlatCString(cmd).get(),
-                                                  PromiseFlatCString(appName).get(),
-                                                  G_APP_INFO_CREATE_SUPPORTS_URIS,
-                                                  &error);
+  if (app_info) {
+    nsGIOMimeApp* app = new nsGIOMimeApp(app_info);
+    NS_ENSURE_TRUE(app, NS_ERROR_OUT_OF_MEMORY);
+    NS_ADDREF(*aAppInfo = app);
+    return NS_OK;
   }
 
+  *aAppInfo = nullptr;
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+/**
+ * Create application info for specified command and application name.
+ * Command arguments are ignored and the "%u" is always added.
+ * @param cmd command to execute
+ * @param appName application name
+ * @param appInfo location where created GAppInfo is stored
+ * @return NS_OK when object is created, NS_ERROR_FILE_NOT_FOUND when executable
+ * is not found in the system path or NS_ERROR_FAILURE otherwise.
+ */
+NS_IMETHODIMP
+nsGIOService::CreateAppFromCommand(nsACString const& cmd,
+                                   nsACString const& appName,
+                                   nsIGIOMimeApp**   appInfo)
+{
+  GError *error = nullptr;
+  *appInfo = nullptr;
+
+  // Using G_APP_INFO_CREATE_SUPPORTS_URIS calling g_app_info_create_from_commandline
+  // appends %u to the cmd even when cmd already contains this parameter.
+  // To avoid that we're going to remove arguments before passing to it.
+  nsAutoCString commandWithoutArgs;
+  nsresult rv = GetCommandFromCommandline(cmd,
+                                          commandWithoutArgs);
+  NS_ENSURE_SUCCESS(rv, rv);
+  GAppInfo *app_info = g_app_info_create_from_commandline(
+      commandWithoutArgs.BeginReading(),
+      PromiseFlatCString(appName).get(),
+      G_APP_INFO_CREATE_SUPPORTS_URIS,
+      &error);
   if (!app_info) {
     g_warning("Cannot create application info from command: %s", error->message);
     g_error_free(error);
     return NS_ERROR_FAILURE;
   }
+
+  // Check if executable exist in path
+  gchar* executableWithFullPath =
+    g_find_program_in_path(commandWithoutArgs.BeginReading());
+  if (!executableWithFullPath) {
+    return NS_ERROR_FILE_NOT_FOUND;
+  }
+  g_free(executableWithFullPath);
+
   nsGIOMimeApp *mozApp = new nsGIOMimeApp(app_info);
   NS_ENSURE_TRUE(mozApp, NS_ERROR_OUT_OF_MEMORY);
   NS_ADDREF(*appInfo = mozApp);
   return NS_OK;
 }
--- a/uriloader/exthandler/nsHandlerService-json.js
+++ b/uriloader/exthandler/nsHandlerService-json.js
@@ -426,16 +426,21 @@ HandlerService.prototype = {
     } else if (handler instanceof Ci.nsIDBusHandlerApp) {
       return {
         name: handler.name,
         service: handler.service,
         method: handler.method,
         objectPath: handler.objectPath,
         dBusInterface: handler.dBusInterface,
       };
+    } else if (handler instanceof Ci.nsIGIOMimeApp) {
+      return {
+        name: handler.name,
+        command: handler.command,
+      };
     }
     // If the handler is an unknown handler type, return null.
     // Android default application handler is the case.
     return null;
   },
 
   /**
    * @param handlerObj
@@ -462,16 +467,25 @@ HandlerService.prototype = {
       handlerApp.uriTemplate = handlerObj.uriTemplate;
     } else if ("service" in handlerObj) {
       handlerApp = Cc["@mozilla.org/uriloader/dbus-handler-app;1"]
                      .createInstance(Ci.nsIDBusHandlerApp);
       handlerApp.service = handlerObj.service;
       handlerApp.method = handlerObj.method;
       handlerApp.objectPath = handlerObj.objectPath;
       handlerApp.dBusInterface = handlerObj.dBusInterface;
+    } else if ("command" in handlerObj &&
+               "@mozilla.org/gio-service;1" in Cc) {
+      try {
+        handlerApp = Cc["@mozilla.org/gio-service;1"]
+                       .getService(Ci.nsIGIOService)
+                       .createAppFromCommand(handlerObj.command, handlerObj.name);
+      } catch (ex) {
+        return null;
+      }
     } else {
       return null;
     }
 
     handlerApp.name = handlerObj.name;
     return handlerApp;
   },
 
--- a/uriloader/exthandler/tests/unit/test_handlerService_json.js
+++ b/uriloader/exthandler/tests/unit/test_handlerService_json.js
@@ -131,8 +131,65 @@ add_task(async function test_migration_r
 
   // Repeat the migration with the JSON file present.
   await unloadHandlerStore();
   await unloadHandlerStoreRDF();
   Services.prefs.setBoolPref("gecko.handlerService.migrated", false);
   await assertAllHandlerInfosMatchDefaultHandlers();
   do_check_true(Services.prefs.getBoolPref("gecko.handlerService.migrated"));
 });
+
+/**
+ * Test saving and reloading an instance of nsIGIOMimeApp.
+ */
+add_task(async function test_store_gioHandlerApp() {
+  if (!("@mozilla.org/gio-service;1" in Cc)) {
+    do_print("Skipping test because it does not apply to this platform.");
+    return;
+  }
+
+  // Create dummy exec file that following won't fail because file not found error
+  let dummyHandlerFile = FileUtils.getFile("TmpD", ["dummyHandler"]);
+  dummyHandlerFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("777", 8));
+
+  // Set up an nsIGIOMimeApp instance for testing.
+  let handlerApp = Cc["@mozilla.org/gio-service;1"]
+                     .getService(Ci.nsIGIOService)
+                     .createAppFromCommand(dummyHandlerFile.path, "Dummy GIO handler");
+  let expectedGIOMimeHandlerApp = {
+    name: handlerApp.name,
+    command: handlerApp.command,
+  };
+
+  await deleteHandlerStore();
+
+  let handlerInfo = getKnownHandlerInfo("example/new");
+  handlerInfo.preferredApplicationHandler = handlerApp;
+  handlerInfo.possibleApplicationHandlers.appendElement(handlerApp);
+  handlerInfo.possibleApplicationHandlers.appendElement(webHandlerApp);
+  gHandlerService.store(handlerInfo);
+
+  await unloadHandlerStore();
+
+  let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
+  HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
+    type: "example/new",
+    preferredAction: Ci.nsIHandlerInfo.saveToDisk,
+    alwaysAskBeforeHandling: false,
+    preferredApplicationHandler: expectedGIOMimeHandlerApp,
+    possibleApplicationHandlers: [expectedGIOMimeHandlerApp, webHandlerApp],
+  });
+
+  await OS.File.remove(dummyHandlerFile.path);
+
+  // After removing dummyHandlerFile, the handler should disappear from the
+  // list of possibleApplicationHandlers and preferredAppHandler should be null.
+  actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
+  HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
+    type: "example/new",
+    preferredAction: Ci.nsIHandlerInfo.saveToDisk,
+    alwaysAskBeforeHandling: false,
+    preferredApplicationHandler: null,
+    possibleApplicationHandlers: [webHandlerApp],
+  });
+
+});
+
--- a/uriloader/exthandler/unix/nsGNOMERegistry.cpp
+++ b/uriloader/exthandler/unix/nsGNOMERegistry.cpp
@@ -39,24 +39,21 @@ nsGNOMERegistry::LoadURL(nsIURI *aURL)
 /* static */ void
 nsGNOMERegistry::GetAppDescForScheme(const nsACString& aScheme,
                                      nsAString& aDesc)
 {
   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
   if (!giovfs)
     return;
 
-  nsAutoCString name;
   nsCOMPtr<nsIGIOMimeApp> app;
   if (NS_FAILED(giovfs->GetAppForURIScheme(aScheme, getter_AddRefs(app))))
     return;
 
-  app->GetName(name);
-
-  CopyUTF8toUTF16(name, aDesc);
+  app->GetName(aDesc);
 }
 
 
 /* static */ already_AddRefed<nsMIMEInfoBase>
 nsGNOMERegistry::GetFromExtension(const nsACString& aFileExt)
 {
   nsAutoCString mimeType;
   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
@@ -80,30 +77,30 @@ nsGNOMERegistry::GetFromExtension(const 
 }
 
 /* static */ already_AddRefed<nsMIMEInfoBase>
 nsGNOMERegistry::GetFromType(const nsACString& aMIMEType)
 {
   RefPtr<nsMIMEInfoUnix> mimeInfo = new nsMIMEInfoUnix(aMIMEType);
   NS_ENSURE_TRUE(mimeInfo, nullptr);
 
-  nsAutoCString name;
+  nsAutoString name;
   nsAutoCString description;
 
   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
   if (!giovfs) {
     return nullptr;
   }
 
   nsCOMPtr<nsIGIOMimeApp> gioHandlerApp;
   if (NS_FAILED(giovfs->GetAppForMimeType(aMIMEType, getter_AddRefs(gioHandlerApp))) ||
       !gioHandlerApp) {
     return nullptr;
   }
   gioHandlerApp->GetName(name);
   giovfs->GetDescriptionForMimeType(aMIMEType, description);
 
-  mimeInfo->SetDefaultDescription(NS_ConvertUTF8toUTF16(name));
+  mimeInfo->SetDefaultDescription(name);
   mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
   mimeInfo->SetDescription(NS_ConvertUTF8toUTF16(description));
 
   return mimeInfo.forget();
 }
--- a/xpcom/system/nsIGIOService.idl
+++ b/xpcom/system/nsIGIOService.idl
@@ -1,31 +1,32 @@
 /* -*- Mode: IDL; 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 "nsISupports.idl"
+#include "nsIMIMEInfo.idl"
 
 interface nsIUTF8StringEnumerator;
 interface nsIURI;
+interface nsIMutableArray;
 
 /* nsIGIOMimeApp holds information about an application that is looked up
    with nsIGIOService::GetAppForMimeType. */
 // 66009894-9877-405b-9321-bf30420e34e6 prev uuid
 
-[scriptable, uuid(ca6bad0c-8a48-48ac-82c7-27bb8f510fbe)] 
-interface nsIGIOMimeApp : nsISupports
+[scriptable, uuid(ca6bad0c-8a48-48ac-82c7-27bb8f510fbe)]
+interface nsIGIOMimeApp : nsIHandlerApp
 {
   const long EXPECTS_URIS  = 0;
   const long EXPECTS_PATHS = 1;
   const long EXPECTS_URIS_FOR_NON_FILES = 2;
 
   readonly attribute AUTF8String         id;
-  readonly attribute AUTF8String         name;
   readonly attribute AUTF8String         command;
   readonly attribute long                expectsURIs;  // see constants above
   readonly attribute nsIUTF8StringEnumerator supportedURISchemes;
 
   void launch(in AUTF8String uri);
   void setAsDefaultForMimeType(in AUTF8String mimeType);
   void setAsDefaultForFileExtensions(in AUTF8String extensions);
   void setAsDefaultForURIScheme(in AUTF8String uriScheme);
@@ -52,23 +53,29 @@ interface nsIGIOService : nsISupports
 
   /* Obtain the MIME type registered for an extension.  The extension
      should not include a leading dot. */
   AUTF8String        getMimeTypeFromExtension(in AUTF8String extension);
 
   /* Obtain the preferred application for opening a given URI scheme */
   nsIGIOMimeApp      getAppForURIScheme(in AUTF8String aURIScheme);
 
+  /* Obtain list of application capable of opening given URI scheme */
+  nsIMutableArray    getAppsForURIScheme(in AUTF8String aURIScheme);
+
   /* Obtain the preferred application for opening a given MIME type */
   nsIGIOMimeApp      getAppForMimeType(in AUTF8String mimeType);
 
-  /* Obtain the preferred application for opening a given MIME type */
-  nsIGIOMimeApp      createAppFromCommand(in AUTF8String cmd, 
+  /* Create application info for given command and name */
+  nsIGIOMimeApp      createAppFromCommand(in AUTF8String cmd,
                                           in AUTF8String appName);
 
+  /* Find the application info by given command */
+  nsIGIOMimeApp      findAppFromCommand(in AUTF8String cmd);
+
   /* Obtain a description for the given MIME type */
   AUTF8String        getDescriptionForMimeType(in AUTF8String mimeType);
 
   /*** Misc. methods ***/
 
   /* Open the given URI in the default application */
   void               showURI(in nsIURI uri);
   [noscript] void    showURIForInput(in ACString uri);