Bug 1411579 - add system handler when Firefox runs in flatpak; r?karlt draft
authorJan Horak <jhorak@redhat.com>
Fri, 12 Jan 2018 16:32:53 +0100
changeset 722101 782c3f7f2aad84dae46636f27a1e9a02d8023f6f
parent 717738 6f5fac320fcb6625603fa8a744ffa8523f8b3d71
child 746524 37dd7e4a9cad48b65b0d90b2c3d48512fcbb4d89
push id96049
push userbmo:jhorak@redhat.com
push dateThu, 18 Jan 2018 13:01:42 +0000
reviewerskarlt
bugs1411579
milestone59.0a1
Bug 1411579 - add system handler when Firefox runs in flatpak; r?karlt Firefox in Flatpak sandboxed environment does not get the list of installed applications on the system because application should know about the environment as little as possible. Introducing nsFlatpakHandlerApp which forwards requests for opening downloaded files to the system by utilizing gtk_show_uri fuction. This changeset also removes nsIGIOMimeApp::Launch method from the interface because it can be fully replaced with LaunchWithUri from nsIHandlerApp interface. The TMPDIR where files are downloaded when user choose to open them needs to be accessible from sandbox and host. The default settings TMPDIR=/tmp is accessible only to the sandbox. To workaround for is to set TMPDIR environment variable to $XDG_CACHE_HOME/tmp before executing Firefox. MozReview-Commit-ID: CSBv0QcETpd
browser/components/shell/nsGNOMEShellService.cpp
toolkit/system/gnome/nsGIOService.cpp
uriloader/exthandler/unix/nsGNOMERegistry.cpp
uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
widget/gtk/mozgtk/mozgtk.c
xpcom/system/nsIGIOService.idl
--- a/browser/components/shell/nsGNOMEShellService.cpp
+++ b/browser/components/shell/nsGNOMEShellService.cpp
@@ -224,18 +224,20 @@ nsGNOMEShellService::IsDefaultBrowser(bo
                                &enabled, handler);
 
       if (!CheckHandlerMatchesAppName(handler) || !enabled)
         return NS_OK; // the handler is disabled or set to another app
     }
 
     if (giovfs) {
       handler.Truncate();
+      nsCOMPtr<nsIHandlerApp> handlerApp;
       giovfs->GetAppForURIScheme(nsDependentCString(appProtocols[i].name),
-                                 getter_AddRefs(gioApp));
+                                 getter_AddRefs(handlerApp));
+      gioApp = do_QueryInterface(handlerApp);
       if (!gioApp)
         return NS_OK;
 
       gioApp->GetCommand(handler);
 
       if (!CheckHandlerMatchesAppName(handler))
         return NS_OK; // the handler is set to another app
     }
@@ -566,20 +568,20 @@ nsGNOMEShellService::OpenApplication(int
     scheme.AssignLiteral("mailto");
   else if (aApplication == APPLICATION_NEWS)
     scheme.AssignLiteral("news");
   else
     return NS_ERROR_NOT_AVAILABLE;
 
   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
   if (giovfs) {
-    nsCOMPtr<nsIGIOMimeApp> gioApp;
-    giovfs->GetAppForURIScheme(scheme, getter_AddRefs(gioApp));
-    if (gioApp)
-      return gioApp->Launch(EmptyCString());
+    nsCOMPtr<nsIHandlerApp> handlerApp;
+    giovfs->GetAppForURIScheme(scheme, getter_AddRefs(handlerApp));
+    if (handlerApp)
+      return handlerApp->LaunchWithURI(nullptr, nullptr);
   }
 
   nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
   if (!gconf)
     return NS_ERROR_FAILURE;
 
   bool enabled;
   nsAutoCString appCommand;
--- a/toolkit/system/gnome/nsGIOService.cpp
+++ b/toolkit/system/gnome/nsGIOService.cpp
@@ -8,24 +8,112 @@
 #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 "nsPrintfCString.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
 
+// We use the same code as gtk_should_use_portal() to detect if we're in flatpak env
+// https://github.com/GNOME/gtk/blob/e0ce028c88858b96aeda9e41734a39a3a04f705d/gtk/gtkprivate.c#L272
+static bool GetShouldUseFlatpakPortal() {
+  bool shouldUsePortal;
+  char *path;
+  path = g_build_filename(g_get_user_runtime_dir(), "flatpak-info", nullptr);
+  if (g_file_test(path, G_FILE_TEST_EXISTS)) {
+    shouldUsePortal = true;
+  } else {
+    shouldUsePortal = (g_getenv("GTK_USE_PORTAL") != nullptr);
+  }
+  g_free(path);
+  return shouldUsePortal;
+}
+
+static bool ShouldUseFlatpakPortal() {
+  static bool sShouldUseFlatpakPortal = GetShouldUseFlatpakPortal();
+  return sShouldUseFlatpakPortal;
+}
+
+class nsFlatpakHandlerApp : public nsIHandlerApp
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIHANDLERAPP
+  nsFlatpakHandlerApp() = default;
+private:
+  virtual ~nsFlatpakHandlerApp() = default;
+
+};
+
+NS_IMPL_ISUPPORTS(nsFlatpakHandlerApp, nsIHandlerApp)
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::GetName(nsAString& aName)
+{
+  aName.AssignLiteral("System Handler");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::SetName(const nsAString& aName)
+{
+  // We don't implement SetName because flatpak system handler name is fixed
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::GetDetailedDescription(nsAString& aDetailedDescription)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::SetDetailedDescription(const nsAString& aDetailedDescription)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::Equals(nsIHandlerApp* aHandlerApp, bool* _retval)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsFlatpakHandlerApp::LaunchWithURI(nsIURI* aUri, nsIInterfaceRequestor* aRequestor)
+{
+  nsCString spec;
+  aUri->GetSpec(spec);
+  GError *error = nullptr;
+
+  // The TMPDIR where files are downloaded when user choose to open them
+  // needs to be accessible from sandbox and host. The default settings
+  // TMPDIR=/tmp is accessible only to the sandbox. That can be the reason
+  // why the gtk_show_uri fails there.
+  // The workaround is to set TMPDIR environment variable in sandbox to
+  // $XDG_CACHE_HOME/tmp before executing Firefox.
+  gtk_show_uri(nullptr, spec.get(), GDK_CURRENT_TIME, &error);
+  if (error) {
+    NS_WARNING(nsPrintfCString("Cannot launch flatpak handler: %s",
+          error->message).get());
+    g_error_free(error);
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
 
 /**
  * Get command without any additional arguments
  * @param aCommandWithArguments full commandline input string
  * @param aCommand string for storing command without arguments
  * @return NS_ERROR_FAILURE when unable to parse commandline
  */
 static nsresult
@@ -96,35 +184,16 @@ nsGIOMimeApp::GetCommand(nsACString& aCo
 NS_IMETHODIMP
 nsGIOMimeApp::GetExpectsURIs(int32_t* aExpects)
 {
   *aExpects = g_app_info_supports_uris(mApp);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsGIOMimeApp::Launch(const nsACString& aUri)
-{
-  GList uris = { 0 };
-  nsPromiseFlatCString flatUri(aUri);
-  uris.data = const_cast<char*>(flatUri.get());
-
-  GError *error = nullptr;
-  gboolean result = g_app_info_launch_uris(mApp, &uris, nullptr, &error);
-
-  if (!result) {
-    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)
 {
@@ -174,19 +243,32 @@ nsGIOMimeApp::Equals(nsIHandlerApp* aHan
   // 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);
+  GList uris = { 0 };
+  nsCString spec;
+  aUri->GetSpec(spec);
+  //nsPromiseFlatCString flatUri(aUri);
+  uris.data = const_cast<char*>(spec.get());
+
+  GError *error = nullptr;
+  gboolean result = g_app_info_launch_uris(mApp, &uris, nullptr, &error);
+
+  if (!result) {
+    g_warning("Cannot launch application: %s", error->message);
+    g_error_free(error);
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
 }
 
 class GIOUTF8StringEnumerator final : public nsIUTF8StringEnumerator
 {
   ~GIOUTF8StringEnumerator() = default;
 
 public:
   GIOUTF8StringEnumerator() : mIndex(0) { }
@@ -362,35 +444,51 @@ nsGIOService::GetMimeTypeFromExtension(c
   g_free(content_type);
 
   return NS_OK;
 }
 // used in nsGNOMERegistry
 // -----------------------------------------------------------------------------
 NS_IMETHODIMP
 nsGIOService::GetAppForURIScheme(const nsACString& aURIScheme,
-                                 nsIGIOMimeApp** aApp)
+                                 nsIHandlerApp** aApp)
 {
   *aApp = nullptr;
 
+  // Application in flatpak sandbox does not have access to the list
+  // of installed applications on the system. We use generic
+  // nsFlatpakHandlerApp which forwards launch call to the system.
+  if (ShouldUseFlatpakPortal()) {
+    nsFlatpakHandlerApp *mozApp = new nsFlatpakHandlerApp();
+    NS_ADDREF(*aApp = mozApp);
+    return NS_OK;
+  }
+
   GAppInfo *app_info = g_app_info_get_default_for_uri_scheme(
                           PromiseFlatCString(aURIScheme).get());
   if (app_info) {
     nsGIOMimeApp *mozApp = new nsGIOMimeApp(app_info);
     NS_ADDREF(*aApp = mozApp);
   } else {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsGIOService::GetAppsForURIScheme(const nsACString& aURIScheme,
                                   nsIMutableArray** aResult)
 {
+  // We don't need to return the nsFlatpakHandlerApp here because
+  // it would be skipped by the callers anyway.
+  // The preferred handler is provided by GetAppForURIScheme.
+  // This method returns all possible application handlers
+  // including preferred one. The callers skips the preferred
+  // handler in this list to avoid duplicate records in the list
+  // they create.
   nsCOMPtr<nsIMutableArray> handlersArray =
     do_CreateInstance(NS_ARRAY_CONTRACTID);
 
   nsAutoCString contentType("x-scheme-handler/");
   contentType.Append(aURIScheme);
 
   GList* appInfoList = g_app_info_get_all_for_type(contentType.get());
   // g_app_info_get_all_for_type returns NULL when no appinfo is found
@@ -406,19 +504,28 @@ nsGIOService::GetAppsForURIScheme(const 
     g_list_free(appInfoList);
   }
   NS_ADDREF(*aResult = handlersArray);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsGIOService::GetAppForMimeType(const nsACString& aMimeType,
-                                nsIGIOMimeApp**   aApp)
+                                nsIHandlerApp**   aApp)
 {
   *aApp = nullptr;
+
+  // Flatpak does not reveal installed application to the sandbox,
+  // we need to create generic system handler.
+  if (ShouldUseFlatpakPortal()) {
+    nsFlatpakHandlerApp *mozApp = new nsFlatpakHandlerApp();
+    NS_ADDREF(*aApp = mozApp);
+    return NS_OK;
+  }
+
   char *content_type =
     g_content_type_from_mime_type(PromiseFlatCString(aMimeType).get());
   if (!content_type)
     return NS_ERROR_FAILURE;
 
   GAppInfo *app_info = g_app_info_get_default_for_type(content_type, false);
   if (app_info) {
     nsGIOMimeApp *mozApp = new nsGIOMimeApp(app_info);
--- a/uriloader/exthandler/unix/nsGNOMERegistry.cpp
+++ b/uriloader/exthandler/unix/nsGNOMERegistry.cpp
@@ -13,17 +13,17 @@
 /* static */ bool
 nsGNOMERegistry::HandlerExists(const char *aProtocolScheme)
 {
   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
   if (!giovfs) {
     return false;
   }
 
-  nsCOMPtr<nsIGIOMimeApp> app;
+  nsCOMPtr<nsIHandlerApp> app;
   return NS_SUCCEEDED(giovfs->GetAppForURIScheme(nsDependentCString(aProtocolScheme),
                                                  getter_AddRefs(app)));
 }
 
 // XXX Check HandlerExists() before calling LoadURL.
 
 /* static */ nsresult
 nsGNOMERegistry::LoadURL(nsIURI *aURL)
@@ -39,17 +39,17 @@ nsGNOMERegistry::LoadURL(nsIURI *aURL)
 /* static */ void
 nsGNOMERegistry::GetAppDescForScheme(const nsACString& aScheme,
                                      nsAString& aDesc)
 {
   nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
   if (!giovfs)
     return;
 
-  nsCOMPtr<nsIGIOMimeApp> app;
+  nsCOMPtr<nsIHandlerApp> app;
   if (NS_FAILED(giovfs->GetAppForURIScheme(aScheme, getter_AddRefs(app))))
     return;
 
   app->GetName(aDesc);
 }
 
 
 /* static */ already_AddRefed<nsMIMEInfoBase>
@@ -85,22 +85,22 @@ nsGNOMERegistry::GetFromType(const nsACS
   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) {
+  nsCOMPtr<nsIHandlerApp> handlerApp;
+  if (NS_FAILED(giovfs->GetAppForMimeType(aMIMEType, getter_AddRefs(handlerApp))) ||
+      !handlerApp) {
     return nullptr;
   }
-  gioHandlerApp->GetName(name);
+  handlerApp->GetName(name);
   giovfs->GetDescriptionForMimeType(aMIMEType, description);
 
   mimeInfo->SetDefaultDescription(name);
   mimeInfo->SetPreferredAction(nsIMIMEInfo::useSystemDefault);
   mimeInfo->SetDescription(NS_ConvertUTF8toUTF16(description));
 
   return mimeInfo.forget();
 }
--- a/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
+++ b/uriloader/exthandler/unix/nsMIMEInfoUnix.cpp
@@ -91,25 +91,23 @@ nsMIMEInfoUnix::LaunchDefaultWithFile(ns
 
   // nsGIOMimeApp->Launch wants a URI string instead of local file
   nsresult rv;
   nsCOMPtr<nsIIOService> ioservice = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
   nsCOMPtr<nsIURI> uri;
   rv = ioservice->NewFileURI(aFile, getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv, rv);
-  nsAutoCString uriSpec;
-  uri->GetSpec(uriSpec);
 
-  nsCOMPtr<nsIGIOMimeApp> app;
+  nsCOMPtr<nsIHandlerApp> app;
   if (NS_FAILED(giovfs->GetAppForMimeType(mSchemeOrType, getter_AddRefs(app))) || !app) {
     return NS_ERROR_FILE_NOT_FOUND;
   }
 
-  return app->Launch(uriSpec);
+  return app->LaunchWithURI(uri, nullptr);
 }
 
 #if defined(MOZ_ENABLE_CONTENTACTION)
 NS_IMETHODIMP
 nsMIMEInfoUnix::GetPossibleApplicationHandlers(nsIMutableArray ** aPossibleAppHandlers)
 {
   if (!mPossibleApplications) {
     mPossibleApplications = do_CreateInstance(NS_ARRAY_CONTRACTID);
--- a/widget/gtk/mozgtk/mozgtk.c
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -386,16 +386,17 @@ STUB(gtk_selection_data_set)
 STUB(gtk_selection_data_set_pixbuf)
 STUB(gtk_selection_data_set_text)
 STUB(gtk_selection_data_targets_include_text)
 STUB(gtk_separator_get_type)
 STUB(gtk_separator_menu_item_new)
 STUB(gtk_separator_tool_item_new)
 STUB(gtk_settings_get_default)
 STUB(gtk_settings_get_for_screen)
+STUB(gtk_show_uri)
 STUB(gtk_socket_add_id)
 STUB(gtk_socket_get_id)
 STUB(gtk_socket_get_type)
 STUB(gtk_socket_get_plug_window)
 STUB(gtk_socket_new)
 STUB(gtk_spin_button_new)
 STUB(gtk_statusbar_new)
 STUB(gtk_style_lookup_icon_set)
--- a/xpcom/system/nsIGIOService.idl
+++ b/xpcom/system/nsIGIOService.idl
@@ -21,17 +21,16 @@ interface nsIGIOMimeApp : nsIHandlerApp
   const long EXPECTS_PATHS = 1;
   const long EXPECTS_URIS_FOR_NON_FILES = 2;
 
   readonly attribute AUTF8String         id;
   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);
 };
 
 /*
  * The VFS service makes use of two distinct registries.
  *
@@ -51,23 +50,23 @@ interface nsIGIOService : nsISupports
 
   /*** MIME registry methods ***/
 
   /* 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);
+  nsIHandlerApp      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);
+  nsIHandlerApp      getAppForMimeType(in AUTF8String mimeType);
 
   /* 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);