[epiphany] Use portals for web app install/uninstall



commit 844358ab54582e10206d514bf81e4a03f1172ab6
Author: Phaedrus Leeds <mwleeds protonmail com>
Date:   Tue Jan 25 20:10:44 2022 -0800

    Use portals for web app install/uninstall
    
    This makes use of the new dynamic launcher portal
    (https://github.com/flatpak/xdg-desktop-portal/pull/696) to install and
    uninstall web apps. This means we are able to support web apps now in
    Flatpak'd Epiphany. Unfortunately it's hard to test though so for
    now remove the unit tests.
    
    Disable the app manager feature under Flatpak for now; a later commit
    will re-enable it.

 lib/ephy-file-helpers.c                          |  19 -
 lib/ephy-file-helpers.h                          |   2 -
 lib/ephy-flatpak-utils.c                         |  38 ++
 lib/ephy-flatpak-utils.h                         |   1 +
 lib/ephy-web-app-utils.c                         | 292 ++++++++-------
 lib/ephy-web-app-utils.h                         |  19 +-
 src/ephy-header-bar.c                            |   8 +-
 src/ephy-main.c                                  |  18 +-
 src/ephy-shell.c                                 |   5 +-
 src/meson.build                                  |   4 +-
 src/preferences/prefs-general-page.c             |  12 +-
 src/profile-migrator/ephy-legacy-web-app-utils.c | 326 +++++++++++++++++
 src/profile-migrator/ephy-profile-migrator.c     |   2 +-
 src/resources/gtk/prefs-general-page.ui          |   6 +-
 src/webapp-provider/ephy-webapp-provider.c       |   9 +-
 src/window-commands.c                            | 438 +++++++++++------------
 tests/ephy-web-app-utils-test.c                  | 171 ---------
 tests/meson.build                                |  11 -
 18 files changed, 774 insertions(+), 607 deletions(-)
---
diff --git a/lib/ephy-file-helpers.c b/lib/ephy-file-helpers.c
index 1ddde58b0..06ad8cbe9 100644
--- a/lib/ephy-file-helpers.c
+++ b/lib/ephy-file-helpers.c
@@ -607,25 +607,6 @@ launch_application (GAppInfo *app,
   return res;
 }
 
-/**
- * ephy_file_launch_webapp_desktop_file:
- * @filename: the path to the .desktop file of a web app
- *
- * Launches the application described by the desktop file @filename.
- *
- * Returns: %TRUE if the application launch was successful
- **/
-gboolean
-ephy_file_launch_webapp_desktop_file (const char *filename,
-                                      guint32     user_time)
-{
-  g_autoptr (GDesktopAppInfo) app = NULL;
-
-  app = g_desktop_app_info_new_from_filename (filename);
-
-  return launch_application (G_APP_INFO (app), NULL, user_time);
-}
-
 static gboolean
 launch_via_uri_handler (GFile *file)
 {
diff --git a/lib/ephy-file-helpers.h b/lib/ephy-file-helpers.h
index 46b7fc459..c089f6d4f 100644
--- a/lib/ephy-file-helpers.h
+++ b/lib/ephy-file-helpers.h
@@ -69,8 +69,6 @@ gboolean           ephy_file_delete_dir_recursively         (const char
 char       *       ephy_sanitize_filename                   (char                  *filename);
 void               ephy_open_default_instance_window        (void);
 void               ephy_open_incognito_window               (const char            *uri);
-gboolean           ephy_file_launch_webapp_desktop_file     (const char            *filepath,
-                                                             guint32                user_time);
 gboolean           ephy_file_open_uri_in_default_browser    (const char            *uri,
                                                              guint32                user_time,
                                                              GdkScreen             *screen);
diff --git a/lib/ephy-flatpak-utils.c b/lib/ephy-flatpak-utils.c
index 351276e54..5b6900001 100644
--- a/lib/ephy-flatpak-utils.c
+++ b/lib/ephy-flatpak-utils.c
@@ -132,3 +132,41 @@ ephy_open_uri_via_flatpak_portal (const char *uri)
 {
   ephy_open_uri (uri, FALSE);
 }
+
+gboolean
+ephy_can_install_web_apps (void)
+{
+  static gsize portal_available = 0;
+  enum {
+    LAUNCHER_PORTAL_MISSING = 1,
+    LAUNCHER_PORTAL_FOUND = 2
+  };
+
+  if (g_once_init_enter (&portal_available)) {
+    g_autoptr (GDBusProxy) proxy = NULL;
+    g_autoptr (GVariant) version = NULL;
+    g_autoptr (GVariant) version_child = NULL;
+    g_autoptr (GVariant) version_grandchild = NULL;
+
+    proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, NULL,
+                                           "org.freedesktop.portal.Desktop",
+                                           "/org/freedesktop/portal/desktop",
+                                           "org.freedesktop.DBus.Properties",
+                                           NULL, NULL);
+    if (proxy)
+      version = g_dbus_proxy_call_sync (proxy, "Get",
+                                        g_variant_new ("(ss)", "org.freedesktop.portal.DynamicLauncher", 
"version"),
+                                        G_DBUS_CALL_FLAGS_NONE,
+                                        -1, NULL, NULL);
+    if (version) {
+      version_child = g_variant_get_child_value (version, 0);
+      version_grandchild = g_variant_get_child_value (version_child, 0);
+      g_debug ("Found version %d of the dynamic launcher portal", g_variant_get_uint32 (version_grandchild));
+      g_once_init_leave (&portal_available, LAUNCHER_PORTAL_FOUND);
+    } else {
+      g_once_init_leave (&portal_available, LAUNCHER_PORTAL_MISSING);
+    }
+  }
+
+  return portal_available == LAUNCHER_PORTAL_FOUND;
+}
diff --git a/lib/ephy-flatpak-utils.h b/lib/ephy-flatpak-utils.h
index 15b85ac9b..289663efb 100644
--- a/lib/ephy-flatpak-utils.h
+++ b/lib/ephy-flatpak-utils.h
@@ -30,3 +30,4 @@ void     ephy_open_uri_via_flatpak_portal                (const char *uri);
 
 void     ephy_open_directory_via_flatpak_portal          (const char *uri);
 
+gboolean ephy_can_install_web_apps                       (void);
diff --git a/lib/ephy-web-app-utils.c b/lib/ephy-web-app-utils.c
index b0cb8af32..7f8f37de8 100644
--- a/lib/ephy-web-app-utils.c
+++ b/lib/ephy-web-app-utils.c
@@ -1,6 +1,7 @@
 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /*
  *  Copyright © 2011 Igalia S.L.
+ *  Copyright © 2022 Matthew Leeds
  *
  *  This file is part of Epiphany.
  *
@@ -33,6 +34,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <fcntl.h>
+#include <libportal-gtk3/portal-gtk3.h>
 
 /* Web apps are installed in the default data dir of the user. Every
  * app has its own profile directory. To create a web app, an ID needs
@@ -201,9 +203,8 @@ ephy_web_application_delete (const char      *id,
   g_autofree char *cache_dir = NULL;
   g_autofree char *config_dir = NULL;
   g_autofree char *desktop_file = NULL;
-  g_autofree char *desktop_path = NULL;
-  g_autoptr (GFile) launcher = NULL;
   g_autoptr (GError) error = NULL;
+  XdpPortal *portal = ephy_get_portal ();
 
   g_assert (id);
 
@@ -253,16 +254,13 @@ ephy_web_application_delete (const char      *id,
     return FALSE;
   }
 
-  desktop_path = g_build_filename (g_get_user_data_dir (), "applications", desktop_file, NULL);
-  if (g_file_test (desktop_path, G_FILE_TEST_IS_SYMLINK)) {
-    launcher = g_file_new_for_path (desktop_path);
-    if (!g_file_delete (launcher, NULL, &error)) {
-      g_warning ("Failed to delete %s: %s", desktop_path, error->message);
-      return FALSE;
-    }
-    LOG ("Deleted application launcher.\n");
+  if (!xdp_portal_dynamic_launcher_uninstall (portal, desktop_file, &error)) {
+    g_warning ("Failed to uninstall desktop file using portal: %s", error->message);
+    return FALSE;
   }
 
+  LOG ("Deleted application launcher %s.\n", desktop_file);
+
   return TRUE;
 }
 
@@ -297,32 +295,27 @@ ephy_web_application_delete_by_desktop_file_id (const char      *desktop_file_id
   return ephy_web_application_delete (id, out_app_found);
 }
 
-static char *
+static gboolean
 create_desktop_file (const char *id,
-                     const char *name,
                      const char *address,
                      const char *profile_dir,
-                     const char *icon_path)
+                     const char *install_token)
 {
   g_autofree char *filename = NULL;
   g_autoptr (GKeyFile) file = NULL;
   g_autofree char *exec_string = NULL;
   g_autofree char *wm_class = NULL;
-  g_autofree char *data = NULL;
-  g_autofree char *desktop_file_path = NULL;
-  g_autofree char *apps_path = NULL;
-  g_autofree char *link_path = NULL;
-  g_autoptr (GFile) link = NULL;
+  g_autofree char *desktop_entry = NULL;
   g_autoptr (GError) error = NULL;
+  XdpPortal *portal = ephy_get_portal ();
 
   g_assert (profile_dir);
 
   filename = get_app_desktop_filename (id);
   if (!filename)
-    return NULL;
+    return FALSE;
 
   file = g_key_file_new ();
-  g_key_file_set_value (file, "Desktop Entry", "Name", name);
   exec_string = g_strdup_printf ("epiphany --application-mode \"--profile=%s\" %s",
                                  profile_dir,
                                  address);
@@ -332,33 +325,23 @@ create_desktop_file (const char *id,
   g_key_file_set_value (file, "Desktop Entry", "Type", "Application");
   g_key_file_set_value (file, "Desktop Entry", "Categories", "GNOME;GTK;");
 
-  if (icon_path)
-    g_key_file_set_value (file, "Desktop Entry", "Icon", icon_path);
-
   wm_class = g_strconcat (EPHY_WEB_APP_GAPPLICATION_ID_PREFIX, id, NULL);
   g_key_file_set_value (file, "Desktop Entry", "StartupWMClass", wm_class);
 
   g_key_file_set_value (file, "Desktop Entry", "X-Purism-FormFactor", "Workstation;Mobile;");
 
-  data = g_key_file_to_data (file, NULL, NULL);
-
-  desktop_file_path = g_build_filename (profile_dir, filename, NULL);
+  desktop_entry = g_key_file_to_data (file, NULL, NULL);
 
-  if (!g_file_set_contents (desktop_file_path, data, -1, NULL))
-    g_clear_pointer (&desktop_file_path, g_free);
-
-  /* Create a symlink in XDG_DATA_DIR/applications for the Shell to
-   * pick up this application. */
-  apps_path = g_build_filename (g_get_user_data_dir (), "applications", NULL);
-  if (ephy_ensure_dir_exists (apps_path, &error)) {
-    link_path = g_build_filename (apps_path, filename, NULL);
-    link = g_file_new_for_path (link_path);
-    g_file_make_symbolic_link (link, desktop_file_path, NULL, NULL);
-  } else {
-    g_warning ("Error creating application symlink: %s", error->message);
+  if (!xdp_portal_dynamic_launcher_install (portal, install_token, filename,
+                                            desktop_entry, &error)) {
+    g_warning ("Failed to install desktop file %s: %s", filename, error->message);
+    ephy_file_delete_dir_recursively (profile_dir, NULL);
+    return FALSE;
   }
 
-  return g_steal_pointer (&desktop_file_path);
+  LOG ("Created application launcher %s.\n", filename);
+
+  return TRUE;
 }
 
 /**
@@ -366,45 +349,35 @@ create_desktop_file (const char *id,
  * @id: the identifier for the new web application
  * @address: the address of the new web application
  * @name: the name for the new web application
- * @icon_pixbuf: the icon for the new web application as a #GdkPixbuf
- * @icon_path: the path to the icon, used instead of @icon_pixbuf
- * @install_token: the install token acquired via portal, used for
- *   non-interactive sandboxed installation
+ * @install_token: the install token acquired via portal methods
  * @options: the options for the new web application
  *
  * Creates a new Web Application for @address.
  *
- * Returns: (transfer-full): the path to the desktop file representing the new application
+ * Returns: %TRUE on success, %FALSE on failure
  **/
-char *
+gboolean
 ephy_web_application_create (const char                *id,
                              const char                *address,
-                             const char                *name,
-                             GdkPixbuf                 *icon_pixbuf,
-                             const char                *icon_path,
                              const char                *install_token,
                              EphyWebApplicationOptions  options)
 {
   g_autofree char *app_file = NULL;
   g_autofree char *profile_dir = NULL;
-  g_autofree char *desktop_file_path = NULL;
-  g_autofree char *icon_path_owned = NULL;
   int fd;
 
-  g_return_val_if_fail (!icon_pixbuf || !icon_path, NULL);
-
   /* If there's already a WebApp profile for the contents of this
    * view, do nothing. */
   profile_dir = ephy_web_application_get_profile_directory (id);
   if (g_file_test (profile_dir, G_FILE_TEST_IS_DIR)) {
     g_warning ("Profile directory %s already exists", profile_dir);
-    return NULL;
+    return FALSE;
   }
 
   /* Create the profile directory, populate it. */
   if (g_mkdir_with_parents (profile_dir, 488) == -1) {
     g_warning ("Failed to create directory %s", profile_dir);
-    return NULL;
+    return FALSE;
   }
 
   /* Skip migration for new web apps. */
@@ -415,30 +388,17 @@ ephy_web_application_create (const char                *id,
   fd = g_open (app_file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
   if (fd < 0) {
     g_warning ("Failed to create .app file: %s", g_strerror (errno));
-    return NULL;
+    return FALSE;
   }
   close (fd);
 
-  /* Write the icon to a file */
-  if (icon_pixbuf) {
-    g_autoptr (GOutputStream) stream = NULL;
-    g_autoptr (GFile) image = NULL;
-
-    icon_path_owned = g_build_filename (profile_dir, EPHY_WEB_APP_ICON_NAME, NULL);
-    image = g_file_new_for_path (icon_path_owned);
-
-    stream = (GOutputStream *)g_file_create (image, 0, NULL, NULL);
-    gdk_pixbuf_save_to_stream (icon_pixbuf, stream, "png", NULL, NULL, NULL);
-  } else {
-    icon_path_owned = g_strdup (icon_path);
-  }
-
   /* Create the deskop file. */
-  desktop_file_path = create_desktop_file (id, name, address, profile_dir, icon_path_owned);
-  if (desktop_file_path)
-    ephy_web_application_initialize_settings (profile_dir, options);
+  if (!create_desktop_file (id, address, profile_dir, install_token))
+    return FALSE;
+
+  ephy_web_application_initialize_settings (profile_dir, options);
 
-  return g_steal_pointer (&desktop_file_path);
+  return TRUE;
 }
 
 char *
@@ -499,14 +459,34 @@ ephy_web_application_ensure_for_app_info (GAppInfo *app_info)
   return g_steal_pointer (&profile_dir);
 }
 
+GKeyFile *
+ephy_web_application_get_desktop_keyfile (const char  *id,
+                                          GError     **error)
+{
+  XdpPortal *portal = ephy_get_portal ();
+  g_autofree char *desktop_basename = NULL;
+  g_autofree char *contents = NULL;
+  g_autoptr (GKeyFile) key_file = NULL;
+
+  desktop_basename = get_app_desktop_filename (id);
+  contents = xdp_portal_dynamic_launcher_get_desktop_entry (portal, desktop_basename, error);
+  if (!contents)
+    return NULL;
+
+  key_file = g_key_file_new ();
+  if (!g_key_file_load_from_data (key_file, contents, -1, G_KEY_FILE_NONE, error))
+    return NULL;
+
+  return g_steal_pointer (&key_file);
+}
+
 void
 ephy_web_application_setup_from_profile_directory (const char *profile_directory)
 {
   const char *gapplication_id;
   const char *id;
-  g_autofree char *desktop_basename = NULL;
-  g_autofree char *desktop_filename = NULL;
-  g_autoptr (GDesktopAppInfo) desktop_info = NULL;
+  g_autoptr (GKeyFile) desktop_keyfile = NULL;
+  g_autoptr (GError) error = NULL;
 
   g_assert (profile_directory != NULL);
 
@@ -522,12 +502,18 @@ ephy_web_application_setup_from_profile_directory (const char *profile_directory
     g_error ("Failed to get app ID from GApplication ID %s", gapplication_id);
 
   /* Get display name from desktop file */
-  desktop_basename = get_app_desktop_filename (id);
-  desktop_filename = g_build_filename (profile_directory, desktop_basename, NULL);
-  desktop_info = g_desktop_app_info_new_from_filename (desktop_filename);
-  if (!desktop_info)
-    g_error ("Required desktop file not present at %s", desktop_filename);
-  g_set_application_name (g_app_info_get_name (G_APP_INFO (desktop_info)));
+  desktop_keyfile = ephy_web_application_get_desktop_keyfile (id, &error);
+  if (!desktop_keyfile) {
+    g_warning ("Required desktop file '%s' not available: %s", gapplication_id, error->message);
+    g_clear_error (&error);
+  } else {
+    g_autofree char *name = NULL;
+    name = g_key_file_get_string (desktop_keyfile, "Desktop Entry", "Name", NULL);
+    if (!name)
+      g_warning ("Missing name in desktop file '%s'", gapplication_id);
+    else
+      g_set_application_name (name);
+  }
 }
 
 void
@@ -542,6 +528,18 @@ ephy_web_application_setup_from_desktop_file (GDesktopAppInfo *desktop_info)
   g_set_application_name (g_app_info_get_display_name (app_info));
 }
 
+void
+ephy_web_application_launch (const char *id)
+{
+  XdpPortal *portal = ephy_get_portal ();
+  g_autofree char *desktop_basename = NULL;
+  g_autoptr (GError) error = NULL;
+
+  desktop_basename = get_app_desktop_filename (id);
+  if (!xdp_portal_dynamic_launcher_launch (portal, desktop_basename, NULL, &error))
+    g_warning ("Failed to launch app '%s': %s", desktop_basename, error->message);
+}
+
 void
 ephy_web_application_free (EphyWebApplication *app)
 {
@@ -550,6 +548,7 @@ ephy_web_application_free (EphyWebApplication *app)
   g_free (app->icon_url);
   g_free (app->url);
   g_free (app->desktop_file);
+  g_free (app->desktop_path);
   g_free (app);
 }
 
@@ -557,7 +556,6 @@ EphyWebApplication *
 ephy_web_application_for_profile_directory (const char *profile_dir)
 {
   g_autoptr (EphyWebApplication) app = NULL;
-  g_autofree char *desktop_file_path = NULL;
   const char *id;
   g_autoptr (GDesktopAppInfo) desktop_info = NULL;
   const char *exec;
@@ -566,6 +564,8 @@ ephy_web_application_for_profile_directory (const char *profile_dir)
   g_autoptr (GFile) file = NULL;
   g_autoptr (GFileInfo) file_info = NULL;
   g_autoptr (GDate) date = NULL;
+  g_autoptr (GError) error = NULL;
+  g_autoptr (GKeyFile) key_file = NULL;
 
   id = get_app_id_from_profile_directory (profile_dir);
   if (!id)
@@ -574,9 +574,42 @@ ephy_web_application_for_profile_directory (const char *profile_dir)
   app = g_new0 (EphyWebApplication, 1);
   app->id = g_strdup (id);
 
-  app->desktop_file = get_app_desktop_filename (id);
-  desktop_file_path = g_build_filename (profile_dir, app->desktop_file, NULL);
-  desktop_info = g_desktop_app_info_new_from_filename (desktop_file_path);
+  app->desktop_path = ephy_web_application_get_desktop_path (id);
+  if (ephy_can_install_web_apps ()) {
+    key_file = ephy_web_application_get_desktop_keyfile (id, &error);
+    if (key_file == NULL) {
+      g_warning ("Failed to get desktop keyfile for id %s from portal: %s", id, error->message);
+      g_clear_pointer (&app, ephy_web_application_free); /* avoid a scan-build warning */
+      return NULL;
+    }
+
+    app->name = g_key_file_get_string (key_file, "Desktop Entry", "Name", NULL);
+    app->icon_url = g_key_file_get_string (key_file, "Desktop Entry", "Icon", NULL);
+
+    exec = g_key_file_get_string (key_file, "Desktop Entry", "Exec", NULL);
+    if (g_shell_parse_argv (exec, &argc, &argv, NULL))
+      app->url = g_strdup (argv[argc - 1]);
+
+    file = g_file_new_for_path (profile_dir);
+
+    /* FIXME: this should use TIME_CREATED but it does not seem to be working. */
+    file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL);
+    app->install_date_uint64 = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+    date = g_date_new ();
+    g_date_set_time_t (date, (time_t)app->install_date_uint64);
+    g_date_strftime (app->install_date, 127, "%x", date);
+
+    return g_steal_pointer (&app);
+  }
+
+  /* sandbox should take the code path above */
+  if (ephy_is_running_inside_sandbox ()) {
+    g_warning ("Epiphany is sandboxed but the DynamicLauncher portal is unavailable; can't use web app 
functionality");
+    return NULL;
+  }
+
+  desktop_info = g_desktop_app_info_new_from_filename (app->desktop_path);
   if (!desktop_info) {
     g_clear_pointer (&app, ephy_web_application_free); /* avoid a scan-build warning */
     return NULL;
@@ -588,7 +621,7 @@ ephy_web_application_for_profile_directory (const char *profile_dir)
   if (g_shell_parse_argv (exec, &argc, &argv, NULL))
     app->url = g_strdup (argv[argc - 1]);
 
-  file = g_file_new_for_path (desktop_file_path);
+  file = g_file_new_for_path (app->desktop_path);
 
   /* FIXME: this should use TIME_CREATED but it does not seem to be working. */
   file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL);
@@ -601,18 +634,24 @@ ephy_web_application_for_profile_directory (const char *profile_dir)
   return g_steal_pointer (&app);
 }
 
-static GList *
-ephy_web_application_get_application_list_internal (gboolean only_legacy)
+/**
+ * ephy_web_application_get_application_list:
+ *
+ * Gets a list of the currently installed web applications.
+ * Free the returned GList with
+ * ephy_web_application_free_application_list.
+ *
+ * Returns: (transfer-full): a #GList of #EphyWebApplication objects
+ **/
+GList *
+ephy_web_application_get_application_list (void)
 {
   g_autoptr (GFileEnumerator) children = NULL;
   GList *applications = NULL;
   g_autofree char *parent_directory_path = NULL;
   g_autoptr (GFile) parent_directory = NULL;
 
-  if (only_legacy)
-    parent_directory_path = g_build_filename (g_get_user_config_dir (), "epiphany", NULL);
-  else
-    parent_directory_path = g_strdup (g_get_user_data_dir ());
+  parent_directory_path = g_strdup (g_get_user_data_dir ());
 
   parent_directory = g_file_new_for_path (parent_directory_path);
   children = g_file_enumerate_children (parent_directory,
@@ -629,19 +668,15 @@ ephy_web_application_get_application_list_internal (gboolean only_legacy)
       break;
 
     name = g_file_info_get_name (info);
-    if ((only_legacy && g_str_has_prefix (name, "app-")) ||
-        (!only_legacy && g_str_has_prefix (name, EPHY_WEB_APP_GAPPLICATION_ID_PREFIX))) {
+    if (g_str_has_prefix (name, EPHY_WEB_APP_GAPPLICATION_ID_PREFIX)) {
       g_autoptr (EphyWebApplication) app = NULL;
       g_autofree char *profile_dir = NULL;
 
       profile_dir = g_build_filename (parent_directory_path, name, NULL);
       app = ephy_web_application_for_profile_directory (profile_dir);
       if (app) {
-        if (!only_legacy) {
-          g_autofree char *app_file = g_build_filename (profile_dir, ".app", NULL);
-          if (g_file_test (app_file, G_FILE_TEST_EXISTS))
-            applications = g_list_prepend (applications, g_steal_pointer (&app));
-        } else
+        g_autofree char *app_file = g_build_filename (profile_dir, ".app", NULL);
+        if (g_file_test (app_file, G_FILE_TEST_EXISTS))
           applications = g_list_prepend (applications, g_steal_pointer (&app));
       }
     }
@@ -650,39 +685,6 @@ ephy_web_application_get_application_list_internal (gboolean only_legacy)
   return g_list_reverse (applications);
 }
 
-/**
- * ephy_web_application_get_application_list:
- *
- * Gets a list of the currently installed web applications.
- * Free the returned GList with
- * ephy_web_application_free_application_list.
- *
- * Returns: (transfer-full): a #GList of #EphyWebApplication objects
- **/
-GList *
-ephy_web_application_get_application_list (void)
-{
-  return ephy_web_application_get_application_list_internal (FALSE);
-}
-
-/**
- * ephy_web_application_get_legacy_application_list:
- *
- * Gets a list of the currently installed web applications.
- * This is only used for the profile migrator as it gets
- * applications in the legacy directory.
- *
- * Free the returned GList with
- * ephy_web_application_free_application_list.
- *
- * Returns: (transfer-full): a #GList of #EphyWebApplication objects
- **/
-GList *
-ephy_web_application_get_legacy_application_list (void)
-{
-  return ephy_web_application_get_application_list_internal (TRUE);
-}
-
 /**
  * ephy_web_application_free_application_list:
  * @list: an #EphyWebApplication GList
@@ -883,15 +885,13 @@ ephy_web_icon_copy_cb (GFile        *file,
 gboolean
 ephy_web_application_save (EphyWebApplication *app)
 {
-  g_autofree char *profile_dir = NULL;
-  g_autofree char *desktop_file_path = NULL;
   g_autofree char *contents = NULL;
   gboolean saved = FALSE;
   g_autoptr (GError) error = NULL;
 
-  profile_dir = ephy_web_application_get_profile_directory (app->id);
-  desktop_file_path = g_build_filename (profile_dir, app->desktop_file, NULL);
-  if (g_file_get_contents (desktop_file_path, &contents, NULL, &error)) {
+  g_assert (!ephy_is_running_inside_sandbox ());
+
+  if (g_file_get_contents (app->desktop_path, &contents, NULL, &error)) {
     g_autoptr (GKeyFile) key = NULL;
     g_autofree char *name = NULL;
     g_autofree char *icon = NULL;
@@ -935,7 +935,7 @@ ephy_web_application_save (EphyWebApplication *app)
     }
 
     if (changed) {
-      saved = g_key_file_save_to_file (key, desktop_file_path, &error);
+      saved = g_key_file_save_to_file (key, app->desktop_path, &error);
       if (!saved)
         g_warning ("Failed to save desktop file of web application: %s\n", error->message);
     }
@@ -962,3 +962,17 @@ ephy_web_application_is_system (EphyWebApplication *app)
 
   return g_settings_get_boolean (web_app_settings, EPHY_PREFS_WEB_APP_SYSTEM);
 }
+
+char *
+ephy_web_application_get_desktop_path (const char *id)
+{
+  g_autofree char *desktop_basename = NULL;
+
+  desktop_basename = get_app_desktop_filename (id);
+
+  /* Note: When running under Flatpak, this path is wrong since the
+   * $XDG_DATA_HOME will be different than on the host. Under Flatpak we only
+   * get the basename from this path and ignore the rest.
+   */
+  return g_build_filename (g_get_user_data_dir (), "applications", desktop_basename, NULL);
+}
diff --git a/lib/ephy-web-app-utils.h b/lib/ephy-web-app-utils.h
index 900b101f5..0616d1bfa 100644
--- a/lib/ephy-web-app-utils.h
+++ b/lib/ephy-web-app-utils.h
@@ -31,7 +31,8 @@ typedef struct {
   char *name;
   char *icon_url;
   char *url;
-  char *desktop_file;
+  char *desktop_file; /* only used for legacy apps */
+  char *desktop_path;
   char install_date[128];
   guint64 install_date_uint64;
 } EphyWebApplication;
@@ -63,16 +64,15 @@ char               *ephy_web_application_get_app_id_from_name (const char *name)
 
 const char         *ephy_web_application_get_gapplication_id_from_profile_directory (const char 
*profile_dir);
 
-char               *ephy_web_application_create (const char                *id,
+gboolean            ephy_web_application_create (const char                *id,
                                                  const char                *address,
-                                                 const char                *name,
-                                                 GdkPixbuf                 *icon_pixbuf,
-                                                 const char                *icon_path,
                                                  const char                *install_token,
                                                  EphyWebApplicationOptions  options);
 
 char               *ephy_web_application_ensure_for_app_info (GAppInfo *app_info);
 
+void                ephy_web_application_launch (const char *id);
+
 gboolean            ephy_web_application_delete (const char      *id,
                                                  EphyWebAppFound *out_app_found);
 
@@ -85,7 +85,13 @@ void                ephy_web_application_setup_from_desktop_file (GDesktopAppInf
 
 char               *ephy_web_application_get_profile_directory (const char *id);
 
-char               *ephy_web_application_get_desktop_path (EphyWebApplication *app);
+/* legacy variant for profile migration */
+char               *ephy_legacy_web_application_get_profile_directory (const char *id);
+
+GKeyFile           *ephy_web_application_get_desktop_keyfile (const char  *id,
+                                                              GError     **error);
+
+char               *ephy_web_application_get_desktop_path (const char *id);
 
 EphyWebApplication *ephy_web_application_for_profile_directory (const char *profile_dir);
 
@@ -95,6 +101,7 @@ gboolean            ephy_web_application_exists (const char *id);
 
 GList              *ephy_web_application_get_application_list (void);
 
+/* legacy variant for profile migration */
 GList              *ephy_web_application_get_legacy_application_list (void);
 
 char              **ephy_web_application_get_desktop_id_list (void);
diff --git a/src/ephy-header-bar.c b/src/ephy-header-bar.c
index f50912828..b03635adf 100644
--- a/src/ephy-header-bar.c
+++ b/src/ephy-header-bar.c
@@ -276,8 +276,6 @@ ephy_header_bar_constructed (GObject *object)
   } else if (ephy_is_running_inside_sandbox ()) {
     gtk_widget_destroy (GTK_WIDGET (gtk_builder_get_object (builder, "run-in-background-separator")));
     gtk_widget_destroy (GTK_WIDGET (gtk_builder_get_object (builder, "run-in-background-button")));
-    gtk_widget_destroy (GTK_WIDGET (gtk_builder_get_object (builder, "save-as-application-separator")));
-    gtk_widget_destroy (GTK_WIDGET (gtk_builder_get_object (builder, "save-as-application-button")));
     gtk_widget_destroy (GTK_WIDGET (gtk_builder_get_object (builder, "application-manager-button")));
 
     if (is_desktop_pantheon ())
@@ -287,6 +285,12 @@ ephy_header_bar_constructed (GObject *object)
     gtk_widget_destroy (GTK_WIDGET (gtk_builder_get_object (builder, "run-in-background-button")));
   }
 
+  if (!ephy_can_install_web_apps ()) {
+    gtk_widget_destroy (GTK_WIDGET (gtk_builder_get_object (builder, "save-as-application-separator")));
+    gtk_widget_destroy (GTK_WIDGET (gtk_builder_get_object (builder, "save-as-application-button")));
+    gtk_widget_destroy (GTK_WIDGET (gtk_builder_get_object (builder, "application-manager-button")));
+  }
+
   header_bar->combined_stop_reload_button = GTK_WIDGET (gtk_builder_get_object (builder, 
"combined_stop_reload_button"));
   header_bar->combined_stop_reload_image = GTK_WIDGET (gtk_builder_get_object (builder, 
"combined_stop_reload_image"));
   gtk_widget_set_tooltip_text (header_bar->combined_stop_reload_button, _(REFRESH_BUTTON_TOOLTIP));
diff --git a/src/ephy-main.c b/src/ephy-main.c
index bcf5ce703..a35a41554 100644
--- a/src/ephy-main.c
+++ b/src/ephy-main.c
@@ -25,6 +25,7 @@
 #include "ephy-debug.h"
 #include "ephy-embed-utils.h"
 #include "ephy-file-helpers.h"
+#include "ephy-flatpak-utils.h"
 #include "ephy-profile-utils.h"
 #include "ephy-session.h"
 #include "ephy-settings.h"
@@ -292,14 +293,19 @@ main (int   argc,
 
   if (application_mode && !profile_directory) {
     if (desktop_file_basename) {
-      desktop_info = g_desktop_app_info_new (desktop_file_basename);
+      if (ephy_is_running_inside_sandbox ()) {
+        g_print ("In sandbox, no desktop file can be passed to --application-mode\n");
+        exit (1);
+      } else {
+        desktop_info = g_desktop_app_info_new (desktop_file_basename);
 
-      if (desktop_info)
-        profile_directory = ephy_web_application_ensure_for_app_info (G_APP_INFO (desktop_info));
+        if (desktop_info)
+          profile_directory = ephy_web_application_ensure_for_app_info (G_APP_INFO (desktop_info));
 
-      if (!profile_directory) {
-        g_print ("Invalid desktop file passed to --application-mode\n");
-        exit (1);
+        if (!profile_directory) {
+          g_print ("Invalid desktop file passed to --application-mode\n");
+          exit (1);
+        }
       }
     }
   }
diff --git a/src/ephy-shell.c b/src/ephy-shell.c
index d356ee168..f016911aa 100644
--- a/src/ephy-shell.c
+++ b/src/ephy-shell.c
@@ -333,10 +333,9 @@ launch_app (GSimpleAction *action,
             GVariant      *parameter,
             gpointer       user_data)
 {
-  const gchar *desktop_file = g_variant_get_string (parameter, NULL);
+  const gchar *webapp_id = g_variant_get_string (parameter, NULL);
 
-  ephy_file_launch_webapp_desktop_file (desktop_file,
-                                        gtk_get_current_event_time ());
+  ephy_web_application_launch (webapp_id);
 }
 
 static void
diff --git a/src/meson.build b/src/meson.build
index 50f6710b3..54964d150 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -73,7 +73,8 @@ libephymain_deps = [
   ephywidgets_dep,
   gvdb_dep,
   libarchive_dep,
-  libhandy_dep
+  libhandy_dep,
+  portal_dep
 ]
 
 libephymain_includes = include_directories(
@@ -103,6 +104,7 @@ ephymain_dep = declare_dependency(
 
 
 ephy_profile_migrator = executable('ephy-profile-migrator',
+  'profile-migrator/ephy-legacy-web-app-utils.c',
   'profile-migrator/ephy-profile-migrator.c',
   dependencies: ephymain_dep,
   install: true,
diff --git a/src/preferences/prefs-general-page.c b/src/preferences/prefs-general-page.c
index 6dc6be6db..6ef42f92b 100644
--- a/src/preferences/prefs-general-page.c
+++ b/src/preferences/prefs-general-page.c
@@ -49,8 +49,11 @@ struct _PrefsGeneralPage {
   guint webapp_save_id;
   GtkWidget *webapp_box;
   GtkWidget *webapp_icon;
+  GtkWidget *webapp_icon_row;
   GtkWidget *webapp_url;
+  GtkWidget *webapp_url_row;
   GtkWidget *webapp_title;
+  GtkWidget *webapp_title_row;
 
   /* Web Content */
   GtkWidget *adblock_allow_switch;
@@ -1081,8 +1084,11 @@ prefs_general_page_class_init (PrefsGeneralPageClass *klass)
   /* Web Application */
   gtk_widget_class_bind_template_child (widget_class, PrefsGeneralPage, webapp_box);
   gtk_widget_class_bind_template_child (widget_class, PrefsGeneralPage, webapp_icon);
+  gtk_widget_class_bind_template_child (widget_class, PrefsGeneralPage, webapp_icon_row);
   gtk_widget_class_bind_template_child (widget_class, PrefsGeneralPage, webapp_url);
+  gtk_widget_class_bind_template_child (widget_class, PrefsGeneralPage, webapp_url_row);
   gtk_widget_class_bind_template_child (widget_class, PrefsGeneralPage, webapp_title);
+  gtk_widget_class_bind_template_child (widget_class, PrefsGeneralPage, webapp_title_row);
 
   /* Web Content */
   gtk_widget_class_bind_template_child (widget_class, PrefsGeneralPage, adblock_allow_switch);
@@ -1168,7 +1174,8 @@ setup_general_page (PrefsGeneralPage *general_page)
   /* ======================================================================== */
   /* ========================== Web Application ============================= */
   /* ======================================================================== */
-  if (ephy_embed_shell_get_mode (ephy_embed_shell_get_default ()) == EPHY_EMBED_SHELL_MODE_APPLICATION) {
+  if (ephy_embed_shell_get_mode (ephy_embed_shell_get_default ()) == EPHY_EMBED_SHELL_MODE_APPLICATION &&
+      !ephy_is_running_inside_sandbox ()) {
     general_page->webapp = ephy_web_application_for_profile_directory (ephy_profile_dir ());
     g_assert (general_page->webapp);
 
@@ -1331,6 +1338,9 @@ prefs_general_page_init (PrefsGeneralPage *general_page)
                           mode == EPHY_EMBED_SHELL_MODE_APPLICATION &&
                           !g_settings_get_boolean (EPHY_SETTINGS_WEB_APP,
                                                    EPHY_PREFS_WEB_APP_SYSTEM));
+  gtk_widget_set_visible (general_page->webapp_icon_row, !ephy_is_running_inside_sandbox ());
+  gtk_widget_set_visible (general_page->webapp_url_row, !ephy_is_running_inside_sandbox ());
+  gtk_widget_set_visible (general_page->webapp_title_row, !ephy_is_running_inside_sandbox ());
   gtk_widget_set_visible (general_page->homepage_box,
                           mode != EPHY_EMBED_SHELL_MODE_APPLICATION);
   gtk_widget_set_visible (general_page->search_engine_group,
diff --git a/src/profile-migrator/ephy-legacy-web-app-utils.c 
b/src/profile-migrator/ephy-legacy-web-app-utils.c
new file mode 100644
index 000000000..09c31ba1a
--- /dev/null
+++ b/src/profile-migrator/ephy-legacy-web-app-utils.c
@@ -0,0 +1,326 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2011 Igalia S.L.
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-web-app-utils.h"
+
+#include "ephy-debug.h"
+#include "ephy-file-helpers.h"
+#include "ephy-profile-utils.h"
+#include "ephy-settings.h"
+
+#include <errno.h>
+#include <gio/gio.h>
+#include <glib/gstdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+/* Web apps are installed in the default data dir of the user. Every
+ * app has its own profile directory. To create a web app, an ID needs
+ * to be generated using the given name as <normalized-name>-checksum.
+ * The ID is used to uniquely identify the app.
+ *
+ *  - Name: the user-visible pretty app name
+ *  - Normalized name: lowercase name
+ *  - Checksum: SHA-1 of name
+ *  - ID: <normalized-name>-<checksum>
+ *  - GApplication ID: see below
+ *  - Profile directory: <gapplication-id>
+ *  - Desktop file: <profile-dir>/<gapplication-id>.desktop
+ *
+ * Note that our ID and GApplication ID are different. Yes, this is confusing.
+ *
+ * For GApplication ID, there are two cases:
+ *
+ *  - If <id> is safe to use in a D-Bus identifier,
+ *    then: GApplication ID is org.gnome.Epiphany.WebApp-<id>
+ *  - otherwise: GAppliation ID is org.gnome.Epiphany.WebApp-<checksum>
+ *
+ * System web applications have a profile dir without a desktop file.
+ */
+
+#define EPHY_WEB_APP_LEGACY_GAPPLICATION_ID_PREFIX "org.gnome.Epiphany.WebApp-"
+
+static char *
+get_encoded_path (const char *path)
+{
+  g_autofree char *encoded = NULL;
+  g_autoptr (GError) error = NULL;
+
+  encoded = g_filename_from_utf8 (path, -1, NULL, NULL, &error);
+  if (error) {
+    g_warning ("%s", error->message);
+    return NULL;
+  }
+
+  return g_steal_pointer (&encoded);
+}
+
+static const char *
+get_app_id_from_gapplication_id (const char *name)
+{
+  if (!g_str_has_prefix (name, EPHY_WEB_APP_LEGACY_GAPPLICATION_ID_PREFIX)) {
+    g_warning ("GApplication ID %s does not begin with required prefix %s", name, 
EPHY_WEB_APP_LEGACY_GAPPLICATION_ID_PREFIX);
+    return NULL;
+  }
+
+  return name + strlen (EPHY_WEB_APP_LEGACY_GAPPLICATION_ID_PREFIX);
+}
+
+static char *
+get_gapplication_id_from_id (const char *id)
+{
+  g_autofree char *gapplication_id = NULL;
+  const char *final_hyphen;
+  const char *checksum;
+
+  /* FIXME: Ideally we would convert hyphens to underscores here, because
+   * hyphens are not very friendly to D-Bus. However, changing this
+   * would require a new profile migration, because the GApplication ID
+   * must exactly match the desktop file basename.
+   *
+   * If we're willing to do another migration in the future, then we
+   * should drop this path and always return just the prefix plus hash,
+   * using underscores rather than hyphens.
+   */
+  gapplication_id = g_strconcat (EPHY_WEB_APP_LEGACY_GAPPLICATION_ID_PREFIX, id, NULL);
+  if (g_application_id_is_valid (gapplication_id))
+    return g_steal_pointer (&gapplication_id);
+
+  /* Split ID into: <normalized-name>-<checksum> */
+  final_hyphen = strrchr (id, '-');
+  if (!final_hyphen) {
+    g_warning ("Web app ID %s is broken: must contain a hyphen", id);
+    return NULL;
+  }
+  checksum = final_hyphen + 1;
+
+  if (*checksum == '\0') {
+    g_warning ("Web app ID %s is broken: should end with checksum, not hyphen", id);
+    return NULL;
+  }
+
+  /* We'll simply omit the <normalized-name> from the app ID, in order
+   * to avoid problematic characters. Ideally we would use an underscore
+   * here too, rather than a hyphen, but let's be consistent with
+   * existing web apps.
+   */
+  g_clear_pointer (&gapplication_id, g_free);
+  gapplication_id = g_strconcat (EPHY_WEB_APP_LEGACY_GAPPLICATION_ID_PREFIX, checksum, NULL);
+
+  if (!g_application_id_is_valid (gapplication_id)) {
+    g_warning ("Web app ID %s is broken: derived GApplication ID %s is not a valid app ID (is the final 
component alphanumeric?)", id, gapplication_id);
+    return NULL;
+  }
+
+  return g_steal_pointer (&gapplication_id);
+}
+
+static char *
+get_app_profile_directory_name (const char *id)
+{
+  g_autofree char *gapplication_id = NULL;
+
+  gapplication_id = get_gapplication_id_from_id (id);
+  if (!gapplication_id)
+    g_error ("Failed to get GApplication ID from app ID %s", id);
+
+  return get_encoded_path (gapplication_id);
+}
+
+static char *
+get_app_desktop_filename (const char *id)
+{
+  g_autofree char *gapplication_id = NULL;
+  g_autofree char *filename = NULL;
+
+  /* Warning: the GApplication ID must exactly match the desktop file's
+   * basename. Don't overthink this or stuff will break, e.g. GNotification.
+   */
+  gapplication_id = get_gapplication_id_from_id (id);
+  if (!gapplication_id)
+    g_error ("Failed to get GApplication ID from app ID %s", id);
+
+  filename = g_strconcat (gapplication_id, ".desktop", NULL);
+  return get_encoded_path (filename);
+}
+
+static const char *
+ephy_legacy_web_application_get_gapplication_id_from_profile_directory (const char *profile_dir)
+{
+  const char *name;
+
+  /* Just get the basename */
+  name = strrchr (profile_dir, G_DIR_SEPARATOR);
+  if (name == NULL) {
+    g_warning ("Profile directory %s is not a valid path", profile_dir);
+    return NULL;
+  }
+
+  name++; /* Strip '/' */
+
+  /* Legacy web app support */
+  if (g_str_has_prefix (name, "app-"))
+    name += strlen ("app-");
+
+  if (!g_str_has_prefix (name, EPHY_WEB_APP_LEGACY_GAPPLICATION_ID_PREFIX)) {
+    g_warning ("Profile directory %s does not begin with required web app prefix %s", profile_dir, 
EPHY_WEB_APP_LEGACY_GAPPLICATION_ID_PREFIX);
+    return NULL;
+  }
+
+  return name;
+}
+
+static const char *
+get_app_id_from_profile_directory (const char *profile_dir)
+{
+  const char *gapplication_id;
+
+  gapplication_id = ephy_legacy_web_application_get_gapplication_id_from_profile_directory (profile_dir);
+  return gapplication_id ? get_app_id_from_gapplication_id (gapplication_id) : NULL;
+}
+
+static char *
+ephy_web_application_get_directory_under (const char *id,
+                                          const char *path)
+{
+  g_autofree char *app_dir = NULL;
+
+  app_dir = get_app_profile_directory_name (id);
+  if (!app_dir)
+    return NULL;
+
+  return g_build_filename (path, app_dir, NULL);
+}
+
+/**
+ * ephy_legacy_web_application_get_profile_directory:
+ * @id: the application identifier
+ *
+ * Gets the directory where the profile for @id is meant to be stored.
+ *
+ * Returns: (transfer full): A newly allocated string.
+ **/
+char *
+ephy_legacy_web_application_get_profile_directory (const char *id)
+{
+  return ephy_web_application_get_directory_under (id, g_get_user_data_dir ());
+}
+
+static EphyWebApplication *
+ephy_legacy_web_application_for_profile_directory (const char *profile_dir)
+{
+  g_autoptr (EphyWebApplication) app = NULL;
+  g_autofree char *desktop_file_path = NULL;
+  const char *id;
+  g_autoptr (GDesktopAppInfo) desktop_info = NULL;
+  const char *exec;
+  int argc;
+  g_auto (GStrv) argv = NULL;
+  g_autoptr (GFile) file = NULL;
+  g_autoptr (GFileInfo) file_info = NULL;
+  guint64 created;
+  g_autoptr (GDate) date = NULL;
+
+  id = get_app_id_from_profile_directory (profile_dir);
+  if (!id)
+    return NULL;
+
+  app = g_new0 (EphyWebApplication, 1);
+  app->id = g_strdup (id);
+
+  app->desktop_file = get_app_desktop_filename (id);
+  desktop_file_path = g_build_filename (profile_dir, app->desktop_file, NULL);
+  desktop_info = g_desktop_app_info_new_from_filename (desktop_file_path);
+  if (!desktop_info) {
+    g_clear_pointer (&app, ephy_web_application_free); /* avoid a scan-build warning */
+    return NULL;
+  }
+
+  app->name = g_strdup (g_app_info_get_name (G_APP_INFO (desktop_info)));
+  app->icon_url = g_desktop_app_info_get_string (desktop_info, "Icon");
+  exec = g_app_info_get_commandline (G_APP_INFO (desktop_info));
+  if (g_shell_parse_argv (exec, &argc, &argv, NULL))
+    app->url = g_strdup (argv[argc - 1]);
+
+  file = g_file_new_for_path (desktop_file_path);
+
+  /* FIXME: this should use TIME_CREATED but it does not seem to be working. */
+  file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL);
+  created = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+  date = g_date_new ();
+  g_date_set_time_t (date, (time_t)created);
+  g_date_strftime (app->install_date, 127, "%x", date);
+
+  return g_steal_pointer (&app);
+}
+
+/**
+ * ephy_web_application_get_legacy_application_list:
+ *
+ * Gets a list of the currently installed legacy web applications.
+ * This is only used for the profile migrator as it gets
+ * applications in the legacy directory.
+ *
+ * Free the returned GList with
+ * ephy_web_application_free_application_list.
+ *
+ * Returns: (transfer-full): a #GList of #EphyWebApplication objects
+ **/
+GList *
+ephy_web_application_get_legacy_application_list (void)
+{
+  g_autoptr (GFileEnumerator) children = NULL;
+  GList *applications = NULL;
+  g_autofree char *parent_directory_path = NULL;
+  g_autoptr (GFile) parent_directory = NULL;
+
+  parent_directory_path = g_build_filename (g_get_user_config_dir (), "epiphany", NULL);
+
+  parent_directory = g_file_new_for_path (parent_directory_path);
+  children = g_file_enumerate_children (parent_directory,
+                                        "standard::name",
+                                        0, NULL, NULL);
+  if (!children)
+    return NULL;
+
+  for (;;) {
+    g_autoptr (GFileInfo) info = g_file_enumerator_next_file (children, NULL, NULL);
+    const char *name;
+
+    if (!info)
+      break;
+
+    name = g_file_info_get_name (info);
+    if (g_str_has_prefix (name, "app-")) {
+      g_autoptr (EphyWebApplication) app = NULL;
+      g_autofree char *profile_dir = NULL;
+
+      profile_dir = g_build_filename (parent_directory_path, name, NULL);
+      app = ephy_legacy_web_application_for_profile_directory (profile_dir);
+      if (app)
+        applications = g_list_prepend (applications, g_steal_pointer (&app));
+    }
+  }
+
+  return g_list_reverse (applications);
+}
diff --git a/src/profile-migrator/ephy-profile-migrator.c b/src/profile-migrator/ephy-profile-migrator.c
index f69c47df0..33939e507 100644
--- a/src/profile-migrator/ephy-profile-migrator.c
+++ b/src/profile-migrator/ephy-profile-migrator.c
@@ -1160,7 +1160,7 @@ migrate_profile_directories (void)
     g_autoptr (GFile) desktop_symlink = NULL;
     g_autofree char *old_name = g_strconcat ("app-epiphany-", app->id, NULL);
     g_autofree char *old_path = g_build_filename (legacy_default_profile_dir (), old_name, NULL);
-    g_autofree char *app_path = ephy_web_application_get_profile_directory (app->id);
+    g_autofree char *app_path = ephy_legacy_web_application_get_profile_directory (app->id);
     g_autofree char *app_file = NULL;
     g_autofree char *old_profile_prefix = NULL;
     g_autofree char *new_profile_prefix = NULL;
diff --git a/src/resources/gtk/prefs-general-page.ui b/src/resources/gtk/prefs-general-page.ui
index 2c9a19ade..6c5541324 100644
--- a/src/resources/gtk/prefs-general-page.ui
+++ b/src/resources/gtk/prefs-general-page.ui
@@ -10,7 +10,7 @@
         <property name="title" translatable="yes">Web Application</property>
         <property name="visible">True</property>
         <child>
-          <object class="HdyActionRow">
+          <object class="HdyActionRow" id="webapp_icon_row">
             <property name="activatable">False</property>
             <property name="title" translatable="yes">_Icon</property>
             <property name="use_underline">True</property>
@@ -33,7 +33,7 @@
           </object>
         </child>
         <child>
-          <object class="HdyActionRow">
+          <object class="HdyActionRow" id="webapp_url_row">
             <property name="activatable">False</property>
             <property name="title" translatable="yes">_Homepage</property>
             <property name="use_underline">True</property>
@@ -48,7 +48,7 @@
           </object>
         </child>
         <child>
-          <object class="HdyActionRow">
+          <object class="HdyActionRow" id="webapp_title_row">
             <property name="activatable">False</property>
             <property name="title" translatable="yes">_Title</property>
             <property name="use_underline">True</property>
diff --git a/src/webapp-provider/ephy-webapp-provider.c b/src/webapp-provider/ephy-webapp-provider.c
index af5fd7e05..be69bd755 100644
--- a/src/webapp-provider/ephy-webapp-provider.c
+++ b/src/webapp-provider/ephy-webapp-provider.c
@@ -134,11 +134,9 @@ handle_install (EphyWebAppProvider        *skeleton,
 
   id = ephy_web_application_get_app_id_from_name (name);
 
-  desktop_path = ephy_web_application_create (id, url, name,
-                                              NULL, NULL, /* icon_pixbuf, icon_path */
-                                              install_token,
-                                              EPHY_WEB_APPLICATION_NONE);
-  if (!desktop_path) {
+  if (!ephy_web_application_create (id, url,
+                                    install_token,
+                                    EPHY_WEB_APPLICATION_NONE)) {
     g_dbus_method_invocation_return_error (invocation, EPHY_WEBAPP_PROVIDER_ERROR,
                                            EPHY_WEBAPP_PROVIDER_ERROR_FAILED,
                                            _("Installing the web application ‘%s’ (%s) failed"),
@@ -146,6 +144,7 @@ handle_install (EphyWebAppProvider        *skeleton,
     goto out;
   }
 
+  desktop_path = ephy_web_application_get_desktop_path (id);
   desktop_file_id = g_path_get_basename (desktop_path);
   ephy_web_app_provider_complete_install (skeleton, invocation, desktop_file_id);
 
diff --git a/src/window-commands.c b/src/window-commands.c
index 2456fef39..32da23f2f 100644
--- a/src/window-commands.c
+++ b/src/window-commands.c
@@ -63,6 +63,7 @@
 #include <gtk/gtk.h>
 #include <string.h>
 #include <webkit2/webkit2.h>
+#include <libportal-gtk3/portal-gtk3.h>
 
 #define DEFAULT_ICON_SIZE 192
 #define FAVICON_SIZE 16
@@ -906,7 +907,7 @@ window_cmd_show_shortcuts (GSimpleAction *action,
     builder = gtk_builder_new_from_resource ("/org/gnome/epiphany/gtk/shortcuts-dialog.ui");
     shortcuts_window = GTK_WIDGET (gtk_builder_get_object (builder, "shortcuts-dialog"));
 
-    if (ephy_is_running_inside_sandbox ())
+    if (!ephy_can_install_web_apps ())
       gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (builder, "shortcuts-web-apps-group")));
 
     if (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL) {
@@ -1374,18 +1375,96 @@ window_cmd_open (GSimpleAction *action,
 
 typedef struct {
   EphyWebView *view;
-  GtkWidget *dialog;
-  GtkWidget *image;
-  GtkWidget *entry;
-  GtkWidget *spinner;
-  GtkWidget *box;
+  const char *display_address;
+  const char *url;
   char *icon_href;
+  char *title;
+  char *chosen_name;
+  char *app_id;
+  char *token;
+  GVariant *icon_v;
   GdkRGBA icon_rgba;
+  GdkPixbuf *framed_pixbuf;
   GCancellable *cancellable;
   EphyWebApplicationOptions webapp_options;
+  gboolean webapp_options_set;
   WebKitDownload *download;
+  EphyWindow *window;
 } EphyApplicationDialogData;
 
+static void
+session_bus_ready_cb (GObject      *source,
+                      GAsyncResult *res,
+                      gpointer      user_data)
+{
+  g_autoptr (GDBusConnection) connection = g_bus_get_finish (res, NULL);
+  g_autofree gchar *desktop_file = user_data;
+  g_autofree gchar *app_id = NULL;
+  GVariant *app;
+
+  if (!connection)
+    return;
+
+  app_id = g_path_get_basename (desktop_file);
+  app = g_variant_new_string (app_id);
+
+  g_dbus_connection_call (connection,
+                          "org.gnome.Shell",
+                          "/org/gnome/Shell",
+                          "org.gnome.Shell",
+                          "FocusApp",
+                          g_variant_new_tuple (&app, 1),
+                          NULL,
+                          G_DBUS_CALL_FLAGS_NO_AUTO_START,
+                          -1,
+                          NULL,
+                          NULL,
+                          NULL);
+}
+
+static void
+ephy_focus_desktop_app (const char *webapp_id)
+{
+  g_autofree char *desktop_path = NULL;
+  /* Note this desktop_path is wrong under Flatpak, but we only use it for its
+   * basename.
+   */
+  desktop_path = ephy_web_application_get_desktop_path (webapp_id);
+  g_bus_get (G_BUS_TYPE_SESSION, NULL, session_bus_ready_cb, g_steal_pointer (&desktop_path));
+}
+
+static void download_finished_cb (WebKitDownload            *download,
+                                  EphyApplicationDialogData *data);
+
+static void download_failed_cb (WebKitDownload            *download,
+                                GError                    *error,
+                                EphyApplicationDialogData *data);
+
+static void
+ephy_application_dialog_data_free (EphyApplicationDialogData *data)
+{
+  if (data->download) {
+    g_signal_handlers_disconnect_by_func (data->download, download_finished_cb, data);
+    g_signal_handlers_disconnect_by_func (data->download, download_failed_cb, data);
+
+    data->download = NULL;
+  }
+
+  g_cancellable_cancel (data->cancellable);
+  g_object_unref (data->cancellable);
+  g_object_unref (data->window);
+  if (data->framed_pixbuf)
+    g_object_unref (data->framed_pixbuf);
+  if (data->icon_v)
+    g_variant_unref (data->icon_v);
+  g_free (data->icon_href);
+  g_free (data->title);
+  g_free (data->chosen_name);
+  g_free (data->token);
+  g_free (data->app_id);
+  g_free (data);
+}
+
 static void
 rounded_rectangle (cairo_t *cr,
                    gdouble  aspect,
@@ -1503,22 +1582,26 @@ frame_pixbuf (GdkPixbuf *pixbuf,
   return framed;
 }
 
+static void create_install_dialog_when_ready (EphyApplicationDialogData *data);
+
 static void
 set_image_from_favicon (EphyApplicationDialogData *data)
 {
-  GdkPixbuf *icon = NULL;
+  g_autoptr (GdkPixbuf) icon = NULL;
   cairo_surface_t *icon_surface = webkit_web_view_get_favicon (WEBKIT_WEB_VIEW (data->view));
 
   if (icon_surface)
     icon = ephy_pixbuf_get_from_surface_scaled (icon_surface, 0, 0);
 
   if (icon != NULL) {
-    GdkPixbuf *framed;
-
-    framed = frame_pixbuf (icon, NULL, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE);
-    g_object_unref (icon);
-    gtk_image_set_from_pixbuf (GTK_IMAGE (data->image), framed);
-    g_object_unref (framed);
+    data->framed_pixbuf = frame_pixbuf (icon, NULL, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE);
+    g_assert (data->icon_v == NULL);
+    data->icon_v = g_icon_serialize (G_ICON (data->framed_pixbuf));
+    create_install_dialog_when_ready (data);
+  }
+  if (data->icon_v == NULL) {
+    g_warning ("Failed to get favicon for web app %s, giving up", data->display_address);
+    ephy_application_dialog_data_free (data);
   }
 }
 
@@ -1526,30 +1609,31 @@ static void
 set_app_icon_from_filename (EphyApplicationDialogData *data,
                             const char                *filename)
 {
-  GdkPixbuf *pixbuf;
-  GdkPixbuf *framed;
+  g_autoptr (GdkPixbuf) pixbuf = NULL;
 
   pixbuf = gdk_pixbuf_new_from_file_at_size (filename, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE, NULL);
-  if (pixbuf == NULL)
-    return;
 
-  framed = frame_pixbuf (pixbuf, &data->icon_rgba, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE);
-  g_object_unref (pixbuf);
-  gtk_image_set_from_pixbuf (GTK_IMAGE (data->image), framed);
-  g_object_unref (framed);
+  if (pixbuf != NULL) {
+    data->framed_pixbuf = frame_pixbuf (pixbuf, &data->icon_rgba, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE);
+    g_assert (data->icon_v == NULL);
+    data->icon_v = g_icon_serialize (G_ICON (data->framed_pixbuf));
+    create_install_dialog_when_ready (data);
+  }
+  if (data->icon_v == NULL) {
+    g_warning ("Failed to get icon for web app %s, giving up", data->display_address);
+    ephy_application_dialog_data_free (data);
+    return;
+  }
 }
 
 static void
 download_finished_cb (WebKitDownload            *download,
                       EphyApplicationDialogData *data)
 {
-  char *filename;
-
-  gtk_widget_show (data->image);
+  g_autofree char *filename = NULL;
 
   filename = g_filename_from_uri (webkit_download_get_destination (download), NULL, NULL);
   set_app_icon_from_filename (data, filename);
-  g_free (filename);
 }
 
 static void
@@ -1557,8 +1641,6 @@ download_failed_cb (WebKitDownload            *download,
                     GError                    *error,
                     EphyApplicationDialogData *data)
 {
-  gtk_widget_show (data->image);
-
   g_signal_handlers_disconnect_by_func (download, download_finished_cb, data);
   /* Something happened, default to a page snapshot. */
   set_image_from_favicon (data);
@@ -1567,7 +1649,9 @@ download_failed_cb (WebKitDownload            *download,
 static void
 download_icon_and_set_image (EphyApplicationDialogData *data)
 {
-  char *destination, *destination_uri, *tmp_filename;
+  g_autofree char *destination = NULL;
+  g_autofree char *destination_uri = NULL;
+  g_autofree char *tmp_filename = NULL;
   EphyEmbedShell *shell = ephy_embed_shell_get_default ();
 
   data->download = webkit_web_context_download_uri (ephy_embed_shell_get_web_context (shell),
@@ -1583,9 +1667,6 @@ download_icon_and_set_image (EphyApplicationDialogData *data)
   destination = g_build_filename (ephy_file_tmp_dir (), tmp_filename, NULL);
   destination_uri = g_filename_to_uri (destination, NULL, NULL);
   webkit_download_set_destination (data->download, destination_uri);
-  g_free (destination);
-  g_free (destination_uri);
-  g_free (tmp_filename);
 
   g_signal_connect (data->download, "finished",
                     G_CALLBACK (download_finished_cb), data);
@@ -1601,7 +1682,7 @@ fill_default_application_image_cb (GObject      *source,
   EphyApplicationDialogData *data = user_data;
   char *uri = NULL;
   GdkRGBA color = { 0.5, 0.5, 0.5, 0.3 };
-  GError *error = NULL;
+  g_autoptr (GError) error = NULL;
 
   ephy_web_view_get_best_web_app_icon_finish (EPHY_WEB_VIEW (source), async_result, &uri, &color, &error);
 
@@ -1612,16 +1693,8 @@ fill_default_application_image_cb (GObject      *source,
   data->icon_rgba = color;
   if (data->icon_href != NULL)
     download_icon_and_set_image (data);
-  else {
-    gtk_widget_show (data->image);
+  else
     set_image_from_favicon (data);
-  }
-}
-
-static void
-fill_default_application_image (EphyApplicationDialogData *data)
-{
-  ephy_web_view_get_best_web_app_icon (data->view, data->cancellable, fill_default_application_image_cb, 
data);
 }
 
 typedef struct {
@@ -1679,7 +1752,8 @@ set_default_application_title (EphyApplicationDialogData *data,
     title = g_strdup (webkit_web_view_get_title (WEBKIT_WEB_VIEW (data->view)));
   }
 
-  gtk_entry_set_text (GTK_ENTRY (data->entry), title);
+  data->title = g_strdup (title);
+  create_install_dialog_when_ready (data);
   g_free (title);
 }
 
@@ -1689,19 +1763,15 @@ fill_default_application_title_cb (GObject      *source,
                                    gpointer      user_data)
 {
   EphyApplicationDialogData *data = user_data;
-  char *title;
-  GError *error = NULL;
+  g_autofree char *title = NULL;
+  g_autoptr (GError) error = NULL;
 
   /* Confusing: this can return NULL for no title, even when there is no error. */
   title = ephy_web_view_get_web_app_title_finish (EPHY_WEB_VIEW (source), async_result, &error);
   if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-    set_default_application_title (data, title);
-}
-
-static void
-fill_default_application_title (EphyApplicationDialogData *data)
-{
-  ephy_web_view_get_web_app_title (data->view, data->cancellable, fill_default_application_title_cb, data);
+    set_default_application_title (data, g_steal_pointer (&title));
+  else
+    ephy_application_dialog_data_free (data);
 }
 
 static void
@@ -1714,116 +1784,52 @@ fill_mobile_capable_cb (GObject      *source,
   gboolean capable;
 
   capable = ephy_web_view_get_web_app_mobile_capable_finish (EPHY_WEB_VIEW (source), async_result, &error);
-  if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+  if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
     data->webapp_options = capable ? EPHY_WEB_APPLICATION_MOBILE_CAPABLE : EPHY_WEB_APPLICATION_NONE;
-}
-
-static void
-fill_mobile_capable (EphyApplicationDialogData *data)
-{
-  ephy_web_view_get_web_app_mobile_capable (data->view, data->cancellable, fill_mobile_capable_cb, data);
-}
-
-static void
-session_bus_ready_cb (GObject      *source,
-                      GAsyncResult *res,
-                      gpointer      user_data)
-{
-  g_autoptr (GDBusConnection) connection = g_bus_get_finish (res, NULL);
-  g_autofree gchar *desktop_file = user_data;
-  g_autofree gchar *app_id = NULL;
-  GVariant *app;
-
-  if (!connection)
-    return;
-
-  app_id = g_path_get_basename (desktop_file);
-  app = g_variant_new_string (app_id);
-
-  g_dbus_connection_call (connection,
-                          "org.gnome.Shell",
-                          "/org/gnome/Shell",
-                          "org.gnome.Shell",
-                          "FocusApp",
-                          g_variant_new_tuple (&app, 1),
-                          NULL,
-                          G_DBUS_CALL_FLAGS_NO_AUTO_START,
-                          -1,
-                          NULL,
-                          NULL,
-                          NULL);
-}
-
-static void
-ephy_focus_desktop_app (const char *desktop_file)
-{
-  g_bus_get (G_BUS_TYPE_SESSION, NULL, session_bus_ready_cb, g_strdup (desktop_file));
-}
-
-static void
-ephy_application_dialog_data_free (EphyApplicationDialogData *data)
-{
-  if (data->download) {
-    g_signal_handlers_disconnect_by_func (data->download, download_finished_cb, data);
-    g_signal_handlers_disconnect_by_func (data->download, download_failed_cb, data);
-
-    data->download = NULL;
+    data->webapp_options_set = TRUE;
+    create_install_dialog_when_ready (data);
+  } else {
+    ephy_application_dialog_data_free (data);
   }
-
-  g_cancellable_cancel (data->cancellable);
-  g_object_unref (data->cancellable);
-  g_free (data->icon_href);
-  g_free (data);
 }
 
 static void
 save_as_application_proceed (EphyApplicationDialogData *data)
 {
-  const char *app_name;
-  g_autofree gchar *app_id = NULL;
-  g_autofree gchar *desktop_file = NULL;
+  g_autofree gchar *desktop_file_id = NULL;
   g_autofree char *message = NULL;
   GNotification *notification;
-
-  app_name = gtk_entry_get_text (GTK_ENTRY (data->entry));
-  app_id = ephy_web_application_get_app_id_from_name (app_name);
+  gboolean success;
 
   /* Create Web Application, including a new profile and .desktop file. */
-  desktop_file = ephy_web_application_create (app_id,
-                                              webkit_web_view_get_uri (WEBKIT_WEB_VIEW (data->view)),
-                                              app_name,
-                                              gtk_image_get_pixbuf (GTK_IMAGE (data->image)),
-                                              NULL, NULL, /* icon_path, install_token */
-                                              data->webapp_options);
-
-  if (desktop_file)
+  success = ephy_web_application_create (data->app_id,
+                                         data->url,
+                                         data->token,
+                                         data->webapp_options);
+
+  if (success)
     message = g_strdup_printf (_("The application “%s” is ready to be used"),
-                               app_name);
+                               data->chosen_name);
   else
     message = g_strdup_printf (_("The application “%s” could not be created"),
-                               app_name);
+                               data->chosen_name);
 
   notification = g_notification_new (message);
 
-  if (data->image) {
-    GdkPixbuf *pixbuf = gtk_image_get_pixbuf (GTK_IMAGE (data->image));
-
-    g_notification_set_icon (notification, G_ICON (pixbuf));
-  }
+  g_notification_set_icon (notification, G_ICON (data->framed_pixbuf));
 
-  if (desktop_file) {
+  if (success) {
     /* Translators: Desktop notification when a new web app is created. */
-    g_notification_add_button_with_target (notification, _("Launch"), "app.launch-app", "s", desktop_file);
-    g_notification_set_default_action_and_target (notification, "app.launch-app", "s", desktop_file);
+    g_notification_add_button_with_target (notification, _("Launch"), "app.launch-app", "s", data->app_id);
+    g_notification_set_default_action_and_target (notification, "app.launch-app", "s", data->app_id);
 
-    ephy_focus_desktop_app (desktop_file);
+    ephy_focus_desktop_app (data->app_id);
   }
 
   g_notification_set_priority (notification, G_NOTIFICATION_PRIORITY_LOW);
 
-  g_application_send_notification (G_APPLICATION (g_application_get_default ()), app_name, notification);
+  g_application_send_notification (G_APPLICATION (g_application_get_default ()), data->chosen_name, 
notification);
 
-  gtk_widget_destroy (GTK_WIDGET (data->dialog));
   ephy_application_dialog_data_free (data);
 }
 
@@ -1832,50 +1838,61 @@ dialog_save_as_application_confirmation_cb (GtkDialog                 *dialog,
                                             GtkResponseType            response,
                                             EphyApplicationDialogData *data)
 {
-  const char *app_name;
-  g_autofree gchar *app_id = NULL;
-
-  app_name = gtk_entry_get_text (GTK_ENTRY (data->entry));
-  app_id = ephy_web_application_get_app_id_from_name (app_name);
-
   gtk_widget_destroy (GTK_WIDGET (dialog));
 
   if (response == GTK_RESPONSE_OK) {
-    ephy_web_application_delete (app_id, NULL);
+    ephy_web_application_delete (data->app_id, NULL);
     save_as_application_proceed (data);
+  } else {
+    ephy_application_dialog_data_free (data);
   }
 }
 
 static void
-dialog_save_as_application_response_cb (GtkDialog                 *dialog,
-                                        GtkResponseType            response,
-                                        EphyApplicationDialogData *data)
+prepare_install_cb (GObject      *object,
+                    GAsyncResult *result,
+                    gpointer      user_data)
 {
-  const char *app_name;
-  g_autofree gchar *app_id = NULL;
+  XdpPortal *portal = XDP_PORTAL (object);
+  EphyApplicationDialogData *data = user_data;
+  g_autoptr (GVariant) ret = NULL;
+  g_autoptr (GVariant) chosen_name_v = NULL;
+  g_autoptr (GVariant) token_v = NULL;
   GtkWidget *confirmation_dialog;
+  g_autoptr (GError) error = NULL;
 
-  if (response != GTK_RESPONSE_OK) {
+  ret = xdp_portal_dynamic_launcher_prepare_install_finish (portal, result, &error);
+  if (ret == NULL) {
+    /* This might just mean the user canceled the operation */
+    g_warning ("Failed to install web app, PrepareInstall() failed: %s", error->message);
+    ephy_application_dialog_data_free (data);
+    return;
+  }
+
+  chosen_name_v = g_variant_lookup_value (ret, "name", G_VARIANT_TYPE_STRING);
+  token_v = g_variant_lookup_value (ret, "token", G_VARIANT_TYPE_STRING);
+  if (chosen_name_v == NULL || token_v == NULL) {
+    g_warning ("Failed to install web app, PrepareInstall() returned invalid data");
     ephy_application_dialog_data_free (data);
-    gtk_widget_destroy (GTK_WIDGET (dialog));
     return;
   }
+  data->chosen_name = g_strdup (g_variant_get_string (chosen_name_v, NULL));
+  data->token = g_strdup (g_variant_get_string (token_v, NULL));
 
-  app_name = gtk_entry_get_text (GTK_ENTRY (data->entry));
-  app_id = ephy_web_application_get_app_id_from_name (app_name);
+  data->app_id = ephy_web_application_get_app_id_from_name (data->chosen_name);
 
-  if (!ephy_web_application_exists (app_id)) {
+  if (!ephy_web_application_exists (data->app_id)) {
     save_as_application_proceed (data);
     return;
   }
 
   confirmation_dialog =
-    gtk_message_dialog_new (GTK_WINDOW (dialog),
+    gtk_message_dialog_new (GTK_WINDOW (data->window),
                             GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
                             GTK_MESSAGE_QUESTION,
                             GTK_BUTTONS_NONE,
                             _("A web application named “%s” already exists. Do you want to replace it?"),
-                            app_name);
+                            data->chosen_name);
   gtk_dialog_add_buttons (GTK_DIALOG (confirmation_dialog),
                           _("Cancel"),
                           GTK_RESPONSE_CANCEL,
@@ -1892,6 +1909,27 @@ dialog_save_as_application_response_cb (GtkDialog                 *dialog,
   gtk_window_present (GTK_WINDOW (confirmation_dialog));
 }
 
+static void
+create_install_dialog_when_ready (EphyApplicationDialogData *data)
+{
+  XdpPortal *portal;
+  XdpParent *parent;
+
+  if (!data->webapp_options_set || !data->title || !data->icon_v)
+    return;
+
+  /* Create a dialog for the user to confirm */
+  portal = ephy_get_portal ();
+  parent = xdp_parent_new_gtk (GTK_WINDOW (data->window));
+  xdp_portal_dynamic_launcher_prepare_install (portal, parent,
+                                               data->title, data->icon_v,
+                                               XDP_LAUNCHER_WEBAPP, data->url,
+                                               TRUE, TRUE, /* editable name, icon */
+                                               data->cancellable,
+                                               prepare_install_cb,
+                                               data);
+}
+
 void
 window_cmd_save_as_application (GSimpleAction *action,
                                 GVariant      *parameter,
@@ -1899,98 +1937,24 @@ window_cmd_save_as_application (GSimpleAction *action,
 {
   EphyWindow *window = user_data;
   EphyEmbed *embed;
-  GtkWidget *dialog, *box, *image, *entry, *content_area;
-  GtkWidget *label;
-  GtkWidget *spinner;
-  EphyWebView *view;
   EphyApplicationDialogData *data;
-  GdkPixbuf *pixbuf;
-  GtkStyleContext *context;
-  char *markup;
-  char *escaped_address;
 
-  if (ephy_is_running_inside_sandbox ())
+  if (!ephy_can_install_web_apps ())
     return;
 
   embed = ephy_embed_container_get_active_child (EPHY_EMBED_CONTAINER (window));
   g_assert (embed != NULL);
 
-  view = EPHY_WEB_VIEW (EPHY_GET_WEBKIT_WEB_VIEW_FROM_EMBED (embed));
-
-  /* Show dialog with icon, title. */
-  dialog = gtk_dialog_new_with_buttons (_("Create Web Application"),
-                                        GTK_WINDOW (window),
-                                        GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | 
GTK_DIALOG_USE_HEADER_BAR,
-                                        _("_Cancel"),
-                                        GTK_RESPONSE_CANCEL,
-                                        _("C_reate"),
-                                        GTK_RESPONSE_OK,
-                                        NULL);
-
-  content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
-
-  box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
-  gtk_widget_set_margin_top (box, 15);
-  gtk_widget_set_margin_bottom (box, 15);
-  gtk_widget_set_margin_start (box, 15);
-  gtk_widget_set_margin_end (box, 15);
-  gtk_container_add (GTK_CONTAINER (content_area), box);
-
-  image = gtk_image_new ();
-  gtk_widget_set_vexpand (image, TRUE);
-  gtk_widget_set_no_show_all (image, TRUE);
-  gtk_widget_set_size_request (image, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE);
-  gtk_widget_set_margin_bottom (image, 10);
-  gtk_container_add (GTK_CONTAINER (box), image);
-  pixbuf = frame_pixbuf (NULL, NULL, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE);
-  gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
-  g_object_unref (pixbuf);
-
-  spinner = gtk_spinner_new ();
-  gtk_widget_set_size_request (spinner, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE);
-  gtk_widget_set_vexpand (spinner, TRUE);
-  gtk_spinner_start (GTK_SPINNER (spinner));
-  gtk_container_add (GTK_CONTAINER (box), spinner);
-  gtk_widget_show (spinner);
-
-  entry = gtk_entry_new ();
-  gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
-  gtk_box_pack_start (GTK_BOX (box), entry, FALSE, TRUE, 0);
-
-  escaped_address = g_markup_escape_text (ephy_web_view_get_display_address (view), -1);
-  markup = g_strdup_printf ("<small>%s</small>", escaped_address);
-  label = gtk_label_new (NULL);
-  gtk_label_set_markup (GTK_LABEL (label), markup);
-  gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
-  gtk_label_set_max_width_chars (GTK_LABEL (label), 40);
-  g_free (markup);
-  g_free (escaped_address);
-
-  gtk_box_pack_start (GTK_BOX (box), label, FALSE, TRUE, 0);
-  context = gtk_widget_get_style_context (label);
-  gtk_style_context_add_class (context, "dim-label");
-
   data = g_new0 (EphyApplicationDialogData, 1);
-  data->dialog = dialog;
-  data->view = view;
-  data->image = image;
-  data->entry = entry;
-  data->spinner = spinner;
+  data->window = g_object_ref (window);
+  data->view = EPHY_WEB_VIEW (EPHY_GET_WEBKIT_WEB_VIEW_FROM_EMBED (embed));
+  data->display_address = ephy_web_view_get_display_address (data->view);
+  data->url = webkit_web_view_get_uri (WEBKIT_WEB_VIEW (data->view));
   data->cancellable = g_cancellable_new ();
 
-  g_object_bind_property (image, "visible", spinner, "visible", G_BINDING_INVERT_BOOLEAN);
-
-  fill_default_application_image (data);
-  fill_default_application_title (data);
-  fill_mobile_capable (data);
-
-  gtk_widget_show_all (dialog);
-
-  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
-  g_signal_connect (dialog, "response",
-                    G_CALLBACK (dialog_save_as_application_response_cb),
-                    data);
-  gtk_widget_show_all (dialog);
+  ephy_web_view_get_best_web_app_icon (data->view, data->cancellable, fill_default_application_image_cb, 
data);
+  ephy_web_view_get_web_app_title (data->view, data->cancellable, fill_default_application_title_cb, data);
+  ephy_web_view_get_web_app_mobile_capable (data->view, data->cancellable, fill_mobile_capable_cb, data);
 }
 
 static char *
diff --git a/tests/meson.build b/tests/meson.build
index fa263370f..d120d1391 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -168,17 +168,6 @@ if get_option('unit_tests').enabled()
        env: envs
   )
 
-  web_app_utils_test = executable('test-ephy-web-app-utils',
-    'ephy-web-app-utils-test.c',
-    dependencies: ephymain_dep,
-    c_args: test_cargs,
-  )
-  test('Web app utils test',
-     web_app_utils_test,
-     env: envs,
-     depends: epiphany
-  )
-
   web_view_test = executable('test-ephy-web-view',
     'ephy-web-view-test.c',
     resources,


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