[epiphany] flatpak: Implement view page source



commit 5d7e8272f00fc817b29fda86aaefd928997af770
Author: Michael Catanzaro <mcatanzaro igalia com>
Date:   Mon Dec 4 11:20:43 2017 -0600

    flatpak: Implement view page source
    
    This was way too hard!
    
    We really need to reimplement in-browser view source mode. But we have
    always supported external view source mode, using the default text/plain
    handler, via a hidden GSetting. So even once we restore in-browser view
    source, we will still want to keep this code around.
    
    We have to use the portal D-Bus API manually because the only way to get
    it using GLib is to use g_app_info_launch_default_for_uri(), and that
    will always pick Epiphany itself as the default handler, because
    Epiphany supports opening HTML files. The portal is only accessible via
    a fallback path.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=787849

 lib/ephy-flatpak-utils.c |  236 ++++++++++++++++++++++++++++++++++++++++++++++
 lib/ephy-flatpak-utils.h |   10 ++-
 src/window-commands.c    |   84 +++++++++++++++--
 3 files changed, 320 insertions(+), 10 deletions(-)
---
diff --git a/lib/ephy-flatpak-utils.c b/lib/ephy-flatpak-utils.c
index 84c06f9..86fff89 100644
--- a/lib/ephy-flatpak-utils.c
+++ b/lib/ephy-flatpak-utils.c
@@ -18,11 +18,247 @@
  *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+/* For O_PATH */
+#define _GNU_SOURCE
+
 #include <config.h>
 #include "ephy-flatpak-utils.h"
 
+#ifdef __linux__
+
+#include <errno.h>
+#include <fcntl.h>
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
 gboolean
 ephy_is_running_inside_flatpak (void)
 {
   return g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS);
 }
+
+static void
+response_cb (GDBusConnection *connection,
+             const char      *sender_name,
+             const char      *object_path,
+             const char      *interface_name,
+             const char      *signal_name,
+             GVariant        *parameters,
+             gpointer         user_data)
+{
+  GTask *task = G_TASK (user_data);
+  guint32 response;
+  guint signal_id;
+
+  signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id"));
+  g_dbus_connection_signal_unsubscribe (connection, signal_id);
+
+  g_task_return_error_if_cancelled (task);
+
+  g_variant_get (parameters, "(u@a{sv})", &response, NULL);
+  if (response == 0)
+    g_task_return_boolean (task, TRUE);
+  else if (response == 1)
+    g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Operation cancelled");
+  else /* yes, this is abuse of G_IO_ERROR, but I don't want to make a new error quark */
+    g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Portal failed to open file");
+}
+
+static void
+open_file_complete_cb (GObject      *source,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  GDBusProxy *proxy = G_DBUS_PROXY (source);
+  GTask *task = G_TASK (user_data);
+  GVariant *return_value = NULL;
+  const char *handle;
+  char *object_path = NULL;
+  GError *error = NULL;
+
+  return_value = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, NULL, result, &error);
+  if (!return_value) {
+    g_warning ("Failed to open file via portal: %s", error->message);
+    g_task_return_error (task, error);
+    goto out;
+  }
+
+  /* Copied from Gio source code. To understand the signal resubscription
+   * dance, refer to the org.freedesktop.portal.Request documentation. */
+  g_variant_get (return_value, "(o)", &object_path);
+  handle = (const char *)g_object_get_data (G_OBJECT (task), "handle");
+  if (strcmp (handle, object_path) != 0) {
+    GDBusConnection *connection;
+    guint signal_id;
+
+    connection = g_dbus_proxy_get_connection (proxy);
+    signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id"));
+    g_dbus_connection_signal_unsubscribe (connection, signal_id);
+
+    signal_id = g_dbus_connection_signal_subscribe (connection,
+                                                    "org.freedesktop.portal.Desktop",
+                                                    "org.freedesktop.portal.Request",
+                                                    "Response",
+                                                    handle,
+                                                    NULL,
+                                                    G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+                                                    response_cb,
+                                                    task,
+                                                    NULL);
+    g_object_set_data (G_OBJECT (task), "signal-id", GUINT_TO_POINTER (signal_id));
+  }
+
+out:
+  if (return_value)
+    g_variant_unref (return_value);
+  if (object_path)
+    g_free (object_path);
+}
+
+static void
+portal_proxy_created_cb (GObject      *source,
+                         GAsyncResult *result,
+                         gpointer      user_data)
+{
+  GTask *task;
+  GDBusProxy *proxy;
+  GVariantBuilder builder;
+  GDBusConnection *connection;
+  GUnixFDList *fd_list;
+  int fd;
+  guint signal_id;
+  char *sender;
+  char *token;
+  char *handle;
+  GError *error = NULL;
+
+  task = G_TASK (user_data);
+  fd = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (task), "fd"));
+
+  proxy = g_dbus_proxy_new_for_bus_finish (result, &error);
+  if (!proxy) {
+    g_warning ("Failed to create D-Bus proxy for OpenURI portal: %s", error->message);
+    g_task_return_error (task, error);
+    close (fd);
+    return;
+  }
+
+  g_object_set_data_full (G_OBJECT (task), "proxy", proxy, g_object_unref);
+
+  /* Refer to org.freedesktop.portal.Request documentation. */
+  connection = g_dbus_proxy_get_connection (proxy);
+  sender = g_strdup (g_dbus_connection_get_unique_name (connection) + 1);
+  for (guint i = 0; sender[i] != '\0'; i++)
+    if (sender[i] == '.')
+      sender[i] = '_';
+  token = g_strdup_printf ("epiphany%u", g_random_int ());
+  handle = g_strdup_printf ("/org/freedesktop/portal/desktop/request/%s/%s", sender, token);
+  g_object_set_data_full (G_OBJECT (task), "handle", handle, g_free);
+  g_free (sender);
+
+  signal_id = g_dbus_connection_signal_subscribe (connection,
+                                                  "org.freedesktop.portal.Desktop",
+                                                  "org.freedesktop.portal.Request",
+                                                  "Response",
+                                                  handle,
+                                                  NULL,
+                                                  G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+                                                  response_cb,
+                                                  task,
+                                                  NULL);
+  g_object_set_data (G_OBJECT (task), "signal-id", GUINT_TO_POINTER (signal_id));
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+  g_variant_builder_add (&builder, "{sv}", "handle_token", g_variant_new_string (token));
+  g_free (token);
+
+  fd_list = g_unix_fd_list_new_from_array (&fd, 1);
+  g_dbus_proxy_call_with_unix_fd_list (proxy,
+                                       "OpenFile",
+                                       g_variant_new ("(s@h@a{sv})",
+                                                      "",
+                                                      g_variant_new("h", 0),
+                                                      g_variant_builder_end (&builder)),
+                                       G_DBUS_CALL_FLAGS_NONE,
+                                       -1,
+                                       fd_list,
+                                       NULL,
+                                       open_file_complete_cb,
+                                       task);
+  g_object_unref (fd_list);
+}
+
+void
+ephy_open_file_via_flatpak_portal (const char          *path,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
+{
+  GTask *task;
+  int fd;
+
+  fd = open (path, O_PATH | O_CLOEXEC);
+  if (fd == -1) {
+    g_warning ("Failed to open %s: %s", path, g_strerror (errno));
+    return;
+  }
+
+  task = g_task_new (NULL, cancellable, callback, user_data);
+  g_object_set_data (G_OBJECT (task), "fd", GINT_TO_POINTER (fd));
+
+  /* We have to do this manually. The recommended solution is to use
+   * g_app_info_launch_default_for_uri(), but that will fail if trying
+   * to open anything that Epiphany itself can open... like text files.
+   * The file will be opened in Epiphany, but we want to get an app
+   * chooser. Otherwise, trying to view page source is just going to open
+   * another browser tab displaying the page in question. Ugh. */
+  g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                            G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+                            NULL,
+                            "org.freedesktop.portal.Desktop",
+                            "/org/freedesktop/portal/desktop",
+                            "org.freedesktop.portal.OpenURI",
+                            NULL,
+                            portal_proxy_created_cb,
+                            task);
+}
+
+gboolean
+ephy_open_file_via_flatpak_portal_finish (GAsyncResult  *result,
+                                          GError       **error)
+{
+  gboolean ret;
+
+  ret = g_task_propagate_boolean (G_TASK (result), error);
+  g_object_unref (result);
+  return ret;
+}
+
+#else /* __linux__ */
+
+gboolean
+ephy_is_running_inside_flatpak (void)
+{
+  return FALSE;
+}
+
+void
+ephy_open_file_via_flatpak_portal (const char          *path,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
+{
+  g_assert_not_reached ();
+}
+
+gboolean
+ephy_open_file_via_flatpak_portal_finish (GAsyncResult  *result,
+                                          GError       **error)
+{
+  g_assert_not_reached ();
+}
+
+#endif /* __linux__ */
diff --git a/lib/ephy-flatpak-utils.h b/lib/ephy-flatpak-utils.h
index f70a4f1..1af19f2 100644
--- a/lib/ephy-flatpak-utils.h
+++ b/lib/ephy-flatpak-utils.h
@@ -20,6 +20,12 @@
 
 #pragma once
 
-#include <glib.h>
+#include <gio/gio.h>
 
-gboolean ephy_is_running_inside_flatpak (void);
+gboolean ephy_is_running_inside_flatpak           (void);
+void     ephy_open_file_via_flatpak_portal        (const char          *path,
+                                                   GCancellable        *cancellable,
+                                                   GAsyncReadyCallback  callback,
+                                                   gpointer             user_data);
+gboolean ephy_open_file_via_flatpak_portal_finish (GAsyncResult        *result,
+                                                   GError             **error);
diff --git a/src/window-commands.c b/src/window-commands.c
index 93d9616..c7e7f56 100644
--- a/src/window-commands.c
+++ b/src/window-commands.c
@@ -40,6 +40,7 @@
 #include "ephy-file-chooser.h"
 #include "ephy-file-helpers.h"
 #include "ephy-find-toolbar.h"
+#include "ephy-flatpak-utils.h"
 #include "ephy-gui.h"
 #include "ephy-header-bar.h"
 #include "ephy-history-dialog.h"
@@ -1832,6 +1833,55 @@ view_source_embedded (const char *uri, EphyEmbed *embed)
 }
 
 static void
+tmp_source_delete_cb (GObject *source, GAsyncResult *result, gpointer data)
+{
+  GFile *file = G_FILE (source);
+  GError *error = NULL;
+
+  if (!g_file_delete_finish (G_FILE (source), result, &error)) {
+    g_warning ("Failed to delete %s: %s", g_file_get_path (file), error->message);
+    g_error_free (error);
+  }
+
+  g_object_unref (file);
+}
+
+static void
+delete_tmp_source_file (GOutputStream *ostream)
+{
+  char *uri;
+  GFile *file;
+
+  g_object_get (G_OBJECT (ostream), "ephy-save-temp-source-uri", &uri, NULL);
+  file = g_file_new_for_uri (uri);
+  g_file_delete_async (file, G_PRIORITY_LOW, NULL, tmp_source_delete_cb, NULL);
+
+  g_free (uri);
+}
+
+static void
+portal_opened_cb (GObject *source, GAsyncResult *result, gpointer data)
+{
+  char *path = data;
+  GFile *file;
+  gboolean ret;
+  GError *error = NULL;
+
+  ret = ephy_open_file_via_flatpak_portal_finish (result, &error);
+  if (!ret) {
+    /* It can be canceled by the user, even without a cancellable. */
+    if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+      g_warning ("Failed to open file via flatpak portal: %s", error->message);
+    g_error_free (error);
+  }
+
+  file = g_file_new_for_path (path);
+  g_free (path);
+
+  g_file_delete_async (file, G_PRIORITY_LOW, NULL, tmp_source_delete_cb, NULL);
+}
+
+static void
 save_temp_source_close_cb (GOutputStream *ostream, GAsyncResult *result, gpointer data)
 {
   const char *uri;
@@ -1842,6 +1892,7 @@ save_temp_source_close_cb (GOutputStream *ostream, GAsyncResult *result, gpointe
   if (error) {
     g_warning ("Unable to close file: %s", error->message);
     g_error_free (error);
+    delete_tmp_source_file (ostream);
     return;
   }
 
@@ -1849,6 +1900,14 @@ save_temp_source_close_cb (GOutputStream *ostream, GAsyncResult *result, gpointe
 
   file = g_file_new_for_uri (uri);
 
+  if (ephy_is_running_inside_flatpak ()) {
+    const char *path;
+
+    path = g_file_get_path (file);
+    ephy_open_file_via_flatpak_portal (path, NULL, portal_opened_cb, g_strdup (path));
+    goto out;
+  }
+
   if (!ephy_file_launch_handler ("text/plain", file, gtk_get_current_event_time ())) {
     /* Fallback to view the source inside the browser */
     EphyEmbed *embed;
@@ -1859,8 +1918,9 @@ save_temp_source_close_cb (GOutputStream *ostream, GAsyncResult *result, gpointe
                                             "ephy-save-temp-source-embed");
     view_source_embedded (uri, embed);
   }
-  g_object_unref (ostream);
 
+out:
+  g_object_unref (ostream);
   g_object_unref (file);
 }
 
@@ -1915,6 +1975,7 @@ get_main_resource_data_cb (WebKitWebResource *resource, GAsyncResult *result, GO
   if (error) {
     g_warning ("Unable to get main resource data: %s", error->message);
     g_error_free (error);
+    delete_tmp_source_file (ostream);
     return;
   }
 
@@ -1974,19 +2035,26 @@ save_temp_source (EphyEmbed *embed,
 {
   GFile *file;
   char *tmp, *base;
-  const char *static_temp_dir;
 
-  static_temp_dir = ephy_file_tmp_dir ();
-  if (static_temp_dir == NULL) {
-    return;
+  if (ephy_is_running_inside_flatpak ()) {
+    /* It has to go here because the portal has no access to our tmpfs.
+     * This means we have to delete it manually! */
+    base = g_build_filename (g_get_user_cache_dir (), "tmp", "viewsourceXXXXXX", NULL);
+  } else {
+    const char *static_temp_dir;
+
+    static_temp_dir = ephy_file_tmp_dir ();
+    if (static_temp_dir == NULL)
+      return;
+
+    base = g_build_filename (static_temp_dir, "viewsourceXXXXXX", NULL);
   }
 
-  base = g_build_filename (static_temp_dir, "viewsourceXXXXXX", NULL);
   tmp = ephy_file_tmp_filename (base, "html");
   g_free (base);
-  if (tmp == NULL) {
+
+  if (tmp == NULL)
     return;
-  }
 
   file = g_file_new_for_path (tmp);
   g_file_replace_async (file, NULL, FALSE,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]