[gnome-software/mwleeds/pwa-plugin] epiphany: Rewrite plugin




commit 3739d5065535ab55ec256f17215f7cfc0144d2e9
Author: Phaedrus Leeds <mwleeds protonmail com>
Date:   Thu Dec 9 16:34:55 2021 -0800

    epiphany: Rewrite plugin
    
    Use the new Epiphany D-Bus API and the new portal, which should make
    this iteration of the plugin less buggy than the previous one.

 lib/gs-plugin-loader.c                             |    4 +-
 lib/gs-utils.c                                     |   31 +
 lib/gs-utils.h                                     |    1 +
 plugins/epiphany/gs-epiphany-generated.c           | 1736 ++++++++++++++++++++
 plugins/epiphany/gs-epiphany-generated.h           |  248 +++
 plugins/epiphany/gs-plugin-epiphany.c              |  749 ++++++---
 plugins/epiphany/gs-plugin-epiphany.h              |   20 +
 plugins/epiphany/gs-self-test.c                    |   42 +-
 plugins/epiphany/meson.build                       |    8 +
 .../epiphany/org.gnome.Epiphany.WebAppProvider.xml |   73 +
 src/gs-installed-page.c                            |    6 +
 11 files changed, 2650 insertions(+), 268 deletions(-)
---
diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c
index 8b0dc7537..4c6513d4f 100644
--- a/lib/gs-plugin-loader.c
+++ b/lib/gs-plugin-loader.c
@@ -1052,7 +1052,9 @@ gs_plugin_loader_app_is_valid (GsApp               *app,
                         gs_plugin_loader_get_app_str (app));
                return FALSE;
        }
-       if (gs_app_get_summary (app) == NULL) {
+       /* web apps usually don't have summaries */
+       if (gs_app_get_kind (app) != AS_COMPONENT_KIND_WEB_APP &&
+           gs_app_get_summary (app) == NULL) {
                g_debug ("app invalid as no summary %s",
                         gs_plugin_loader_get_app_str (app));
                return FALSE;
diff --git a/lib/gs-utils.c b/lib/gs-utils.c
index 47b6ded61..c57c381dc 100644
--- a/lib/gs-utils.c
+++ b/lib/gs-utils.c
@@ -41,6 +41,7 @@
 #include "gs-app.h"
 #include "gs-utils.h"
 #include "gs-plugin.h"
+#include "gs-icon.h"
 
 #define MB_IN_BYTES (1024 * 1024)
 
@@ -1439,6 +1440,36 @@ gs_utils_pixbuf_blur (GdkPixbuf *src, guint radius, guint iterations)
                gs_pixbuf_blur_private (src, tmp, radius, div_kernel_size);
 }
 
+/**
+ * gs_utils_file_icon_ensure_size:
+ * @icon: the #GIcon created with g_file_icon_new()
+ *
+ * This ensures that gs_icon_set_width() and gs_icon_set_height() have been
+ * called on @icon, by loading the width and height from the underlying file if
+ * needed.
+ **/
+void
+gs_utils_file_icon_ensure_size (GIcon *icon)
+{
+       GFile *file;
+       g_autofree char *file_path = NULL;
+       g_autoptr(GdkPixbuf) pixbuf = NULL;
+
+       if (gs_icon_get_width (icon) != 0)
+               return;
+
+       file = g_file_icon_get_file (G_FILE_ICON (icon));
+       g_assert (G_IS_FILE (file));
+       file_path = g_file_get_path (file);
+       pixbuf = gdk_pixbuf_new_from_file (file_path, NULL);
+       if (pixbuf == NULL)
+               g_warning ("%s: Failed to load pixbuf from %s", G_STRFUNC, file_path);
+       else {
+               gs_icon_set_width (icon, gdk_pixbuf_get_width (pixbuf));
+               gs_icon_set_height (icon, gdk_pixbuf_get_height (pixbuf));
+       }
+}
+
 /**
  * gs_utils_get_file_size:
  * @filename: a file name to get the size of; it can be a file or a directory
diff --git a/lib/gs-utils.h b/lib/gs-utils.h
index b5f590df8..ba9898737 100644
--- a/lib/gs-utils.h
+++ b/lib/gs-utils.h
@@ -111,6 +111,7 @@ gchar               *gs_utils_build_unique_id       (AsComponentScope scope,
 void            gs_utils_pixbuf_blur           (GdkPixbuf      *src,
                                                 guint          radius,
                                                 guint          iterations);
+void            gs_utils_file_icon_ensure_size (GIcon  *icon);
 
 /**
  * GsFileSizeIncludeFunc:
diff --git a/plugins/epiphany/gs-epiphany-generated.c b/plugins/epiphany/gs-epiphany-generated.c
new file mode 100644
index 000000000..3d1c224be
--- /dev/null
+++ b/plugins/epiphany/gs-epiphany-generated.c
@@ -0,0 +1,1736 @@
+/*
+ * This file is generated by gdbus-codegen, do not modify it.
+ *
+ * The license of this code is the same as for the D-Bus interface description
+ * it was derived from. Note that it links to GLib, so must comply with the
+ * LGPL linking clauses.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include "gs-epiphany-generated.h"
+
+#include <string.h>
+#ifdef G_OS_UNIX
+#  include <gio/gunixfdlist.h>
+#endif
+
+typedef struct
+{
+  GDBusArgInfo parent_struct;
+  gboolean use_gvariant;
+} _ExtendedGDBusArgInfo;
+
+typedef struct
+{
+  GDBusMethodInfo parent_struct;
+  const gchar *signal_name;
+  gboolean pass_fdlist;
+} _ExtendedGDBusMethodInfo;
+
+typedef struct
+{
+  GDBusSignalInfo parent_struct;
+  const gchar *signal_name;
+} _ExtendedGDBusSignalInfo;
+
+typedef struct
+{
+  GDBusPropertyInfo parent_struct;
+  const gchar *hyphen_name;
+  guint use_gvariant : 1;
+  guint emits_changed_signal : 1;
+} _ExtendedGDBusPropertyInfo;
+
+typedef struct
+{
+  GDBusInterfaceInfo parent_struct;
+  const gchar *hyphen_name;
+} _ExtendedGDBusInterfaceInfo;
+
+typedef struct
+{
+  const _ExtendedGDBusPropertyInfo *info;
+  guint prop_id;
+  GValue orig_value; /* the value before the change */
+} ChangedProperty;
+
+static void
+_changed_property_free (ChangedProperty *data)
+{
+  g_value_unset (&data->orig_value);
+  g_free (data);
+}
+
+static gboolean
+_g_strv_equal0 (gchar **a, gchar **b)
+{
+  gboolean ret = FALSE;
+  guint n;
+  if (a == NULL && b == NULL)
+    {
+      ret = TRUE;
+      goto out;
+    }
+  if (a == NULL || b == NULL)
+    goto out;
+  if (g_strv_length (a) != g_strv_length (b))
+    goto out;
+  for (n = 0; a[n] != NULL; n++)
+    if (g_strcmp0 (a[n], b[n]) != 0)
+      goto out;
+  ret = TRUE;
+out:
+  return ret;
+}
+
+static gboolean
+_g_variant_equal0 (GVariant *a, GVariant *b)
+{
+  gboolean ret = FALSE;
+  if (a == NULL && b == NULL)
+    {
+      ret = TRUE;
+      goto out;
+    }
+  if (a == NULL || b == NULL)
+    goto out;
+  ret = g_variant_equal (a, b);
+out:
+  return ret;
+}
+
+G_GNUC_UNUSED static gboolean
+_g_value_equal (const GValue *a, const GValue *b)
+{
+  gboolean ret = FALSE;
+  g_assert (G_VALUE_TYPE (a) == G_VALUE_TYPE (b));
+  switch (G_VALUE_TYPE (a))
+    {
+      case G_TYPE_BOOLEAN:
+        ret = (g_value_get_boolean (a) == g_value_get_boolean (b));
+        break;
+      case G_TYPE_UCHAR:
+        ret = (g_value_get_uchar (a) == g_value_get_uchar (b));
+        break;
+      case G_TYPE_INT:
+        ret = (g_value_get_int (a) == g_value_get_int (b));
+        break;
+      case G_TYPE_UINT:
+        ret = (g_value_get_uint (a) == g_value_get_uint (b));
+        break;
+      case G_TYPE_INT64:
+        ret = (g_value_get_int64 (a) == g_value_get_int64 (b));
+        break;
+      case G_TYPE_UINT64:
+        ret = (g_value_get_uint64 (a) == g_value_get_uint64 (b));
+        break;
+      case G_TYPE_DOUBLE:
+        {
+          /* Avoid -Wfloat-equal warnings by doing a direct bit compare */
+          gdouble da = g_value_get_double (a);
+          gdouble db = g_value_get_double (b);
+          ret = memcmp (&da, &db, sizeof (gdouble)) == 0;
+        }
+        break;
+      case G_TYPE_STRING:
+        ret = (g_strcmp0 (g_value_get_string (a), g_value_get_string (b)) == 0);
+        break;
+      case G_TYPE_VARIANT:
+        ret = _g_variant_equal0 (g_value_get_variant (a), g_value_get_variant (b));
+        break;
+      default:
+        if (G_VALUE_TYPE (a) == G_TYPE_STRV)
+          ret = _g_strv_equal0 (g_value_get_boxed (a), g_value_get_boxed (b));
+        else
+          g_critical ("_g_value_equal() does not handle type %s", g_type_name (G_VALUE_TYPE (a)));
+        break;
+    }
+  return ret;
+}
+
+/* ------------------------------------------------------------------------
+ * Code for interface org.gnome.Epiphany.WebAppProvider
+ * ------------------------------------------------------------------------
+ */
+
+/**
+ * SECTION:GsEphyWebAppProvider
+ * @title: GsEphyWebAppProvider
+ * @short_description: Generated C code for the org.gnome.Epiphany.WebAppProvider D-Bus interface
+ *
+ * This section contains code for working with the <link 
linkend="gdbus-interface-org-gnome-Epiphany-WebAppProvider.top_of_page">org.gnome.Epiphany.WebAppProvider</link>
 D-Bus interface in C.
+ */
+
+/* ---- Introspection data for org.gnome.Epiphany.WebAppProvider ---- */
+
+static const _ExtendedGDBusArgInfo _gs_ephy_web_app_provider_method_info_get_installed_apps_OUT_ARG_webapps =
+{
+  {
+    -1,
+    (gchar *) "webapps",
+    (gchar *) "as",
+    NULL
+  },
+  FALSE
+};
+
+static const GDBusArgInfo * const 
_gs_ephy_web_app_provider_method_info_get_installed_apps_OUT_ARG_pointers[] =
+{
+  &_gs_ephy_web_app_provider_method_info_get_installed_apps_OUT_ARG_webapps.parent_struct,
+  NULL
+};
+
+static const _ExtendedGDBusMethodInfo _gs_ephy_web_app_provider_method_info_get_installed_apps =
+{
+  {
+    -1,
+    (gchar *) "GetInstalledApps",
+    NULL,
+    (GDBusArgInfo **) &_gs_ephy_web_app_provider_method_info_get_installed_apps_OUT_ARG_pointers,
+    NULL
+  },
+  "handle-get-installed-apps",
+  FALSE
+};
+
+static const _ExtendedGDBusArgInfo _gs_ephy_web_app_provider_method_info_install_IN_ARG_url =
+{
+  {
+    -1,
+    (gchar *) "url",
+    (gchar *) "s",
+    NULL
+  },
+  FALSE
+};
+
+static const _ExtendedGDBusArgInfo _gs_ephy_web_app_provider_method_info_install_IN_ARG_name =
+{
+  {
+    -1,
+    (gchar *) "name",
+    (gchar *) "s",
+    NULL
+  },
+  FALSE
+};
+
+static const _ExtendedGDBusArgInfo _gs_ephy_web_app_provider_method_info_install_IN_ARG_install_token =
+{
+  {
+    -1,
+    (gchar *) "install_token",
+    (gchar *) "s",
+    NULL
+  },
+  FALSE
+};
+
+static const GDBusArgInfo * const _gs_ephy_web_app_provider_method_info_install_IN_ARG_pointers[] =
+{
+  &_gs_ephy_web_app_provider_method_info_install_IN_ARG_url.parent_struct,
+  &_gs_ephy_web_app_provider_method_info_install_IN_ARG_name.parent_struct,
+  &_gs_ephy_web_app_provider_method_info_install_IN_ARG_install_token.parent_struct,
+  NULL
+};
+
+static const _ExtendedGDBusMethodInfo _gs_ephy_web_app_provider_method_info_install =
+{
+  {
+    -1,
+    (gchar *) "Install",
+    (GDBusArgInfo **) &_gs_ephy_web_app_provider_method_info_install_IN_ARG_pointers,
+    NULL,
+    NULL
+  },
+  "handle-install",
+  FALSE
+};
+
+static const _ExtendedGDBusArgInfo _gs_ephy_web_app_provider_method_info_uninstall_IN_ARG_desktop_path =
+{
+  {
+    -1,
+    (gchar *) "desktop_path",
+    (gchar *) "s",
+    NULL
+  },
+  FALSE
+};
+
+static const GDBusArgInfo * const _gs_ephy_web_app_provider_method_info_uninstall_IN_ARG_pointers[] =
+{
+  &_gs_ephy_web_app_provider_method_info_uninstall_IN_ARG_desktop_path.parent_struct,
+  NULL
+};
+
+static const _ExtendedGDBusMethodInfo _gs_ephy_web_app_provider_method_info_uninstall =
+{
+  {
+    -1,
+    (gchar *) "Uninstall",
+    (GDBusArgInfo **) &_gs_ephy_web_app_provider_method_info_uninstall_IN_ARG_pointers,
+    NULL,
+    NULL
+  },
+  "handle-uninstall",
+  FALSE
+};
+
+static const GDBusMethodInfo * const _gs_ephy_web_app_provider_method_info_pointers[] =
+{
+  &_gs_ephy_web_app_provider_method_info_get_installed_apps.parent_struct,
+  &_gs_ephy_web_app_provider_method_info_install.parent_struct,
+  &_gs_ephy_web_app_provider_method_info_uninstall.parent_struct,
+  NULL
+};
+
+static const _ExtendedGDBusPropertyInfo _gs_ephy_web_app_provider_property_info_version =
+{
+  {
+    -1,
+    (gchar *) "version",
+    (gchar *) "u",
+    G_DBUS_PROPERTY_INFO_FLAGS_READABLE,
+    NULL
+  },
+  "version",
+  FALSE,
+  TRUE
+};
+
+static const GDBusPropertyInfo * const _gs_ephy_web_app_provider_property_info_pointers[] =
+{
+  &_gs_ephy_web_app_provider_property_info_version.parent_struct,
+  NULL
+};
+
+static const _ExtendedGDBusInterfaceInfo _gs_ephy_web_app_provider_interface_info =
+{
+  {
+    -1,
+    (gchar *) "org.gnome.Epiphany.WebAppProvider",
+    (GDBusMethodInfo **) &_gs_ephy_web_app_provider_method_info_pointers,
+    NULL,
+    (GDBusPropertyInfo **) &_gs_ephy_web_app_provider_property_info_pointers,
+    NULL
+  },
+  "web-app-provider",
+};
+
+
+/**
+ * gs_ephy_web_app_provider_interface_info:
+ *
+ * Gets a machine-readable description of the <link 
linkend="gdbus-interface-org-gnome-Epiphany-WebAppProvider.top_of_page">org.gnome.Epiphany.WebAppProvider</link>
 D-Bus interface.
+ *
+ * Returns: (transfer none): A #GDBusInterfaceInfo. Do not free.
+ */
+GDBusInterfaceInfo *
+gs_ephy_web_app_provider_interface_info (void)
+{
+  return (GDBusInterfaceInfo *) &_gs_ephy_web_app_provider_interface_info.parent_struct;
+}
+
+/**
+ * gs_ephy_web_app_provider_override_properties:
+ * @klass: The class structure for a #GObject derived class.
+ * @property_id_begin: The property id to assign to the first overridden property.
+ *
+ * Overrides all #GObject properties in the #GsEphyWebAppProvider interface for a concrete class.
+ * The properties are overridden in the order they are defined.
+ *
+ * Returns: The last property id.
+ */
+guint
+gs_ephy_web_app_provider_override_properties (GObjectClass *klass, guint property_id_begin)
+{
+  g_object_class_override_property (klass, property_id_begin++, "version");
+  return property_id_begin - 1;
+}
+
+
+
+/**
+ * GsEphyWebAppProvider:
+ *
+ * Abstract interface type for the D-Bus interface <link 
linkend="gdbus-interface-org-gnome-Epiphany-WebAppProvider.top_of_page">org.gnome.Epiphany.WebAppProvider</link>.
+ */
+
+/**
+ * GsEphyWebAppProviderIface:
+ * @parent_iface: The parent interface.
+ * @handle_get_installed_apps: Handler for the #GsEphyWebAppProvider::handle-get-installed-apps signal.
+ * @handle_install: Handler for the #GsEphyWebAppProvider::handle-install signal.
+ * @handle_uninstall: Handler for the #GsEphyWebAppProvider::handle-uninstall signal.
+ * @get_version: Getter for the #GsEphyWebAppProvider:version property.
+ *
+ * Virtual table for the D-Bus interface <link 
linkend="gdbus-interface-org-gnome-Epiphany-WebAppProvider.top_of_page">org.gnome.Epiphany.WebAppProvider</link>.
+ */
+
+typedef GsEphyWebAppProviderIface GsEphyWebAppProviderInterface;
+G_DEFINE_INTERFACE (GsEphyWebAppProvider, gs_ephy_web_app_provider, G_TYPE_OBJECT)
+
+static void
+gs_ephy_web_app_provider_default_init (GsEphyWebAppProviderIface *iface)
+{
+  /* GObject signals for incoming D-Bus method calls: */
+  /**
+   * GsEphyWebAppProvider::handle-get-installed-apps:
+   * @object: A #GsEphyWebAppProvider.
+   * @invocation: A #GDBusMethodInvocation.
+   *
+   * Signal emitted when a remote caller is invoking the <link 
linkend="gdbus-method-org-gnome-Epiphany-WebAppProvider.GetInstalledApps">GetInstalledApps()</link> D-Bus 
method.
+   *
+   * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a 
reference to @invocation and eventually call gs_ephy_web_app_provider_complete_get_installed_apps() or e.g. 
g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler 
handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned.
+   *
+   * Returns: %G_DBUS_METHOD_INVOCATION_HANDLED or %TRUE if the invocation was handled, 
%G_DBUS_METHOD_INVOCATION_UNHANDLED or %FALSE to let other signal handlers run.
+   */
+  g_signal_new ("handle-get-installed-apps",
+    G_TYPE_FROM_INTERFACE (iface),
+    G_SIGNAL_RUN_LAST,
+    G_STRUCT_OFFSET (GsEphyWebAppProviderIface, handle_get_installed_apps),
+    g_signal_accumulator_true_handled,
+    NULL,
+    g_cclosure_marshal_generic,
+    G_TYPE_BOOLEAN,
+    1,
+    G_TYPE_DBUS_METHOD_INVOCATION);
+
+  /**
+   * GsEphyWebAppProvider::handle-install:
+   * @object: A #GsEphyWebAppProvider.
+   * @invocation: A #GDBusMethodInvocation.
+   * @arg_url: Argument passed by remote caller.
+   * @arg_name: Argument passed by remote caller.
+   * @arg_install_token: Argument passed by remote caller.
+   *
+   * Signal emitted when a remote caller is invoking the <link 
linkend="gdbus-method-org-gnome-Epiphany-WebAppProvider.Install">Install()</link> D-Bus method.
+   *
+   * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a 
reference to @invocation and eventually call gs_ephy_web_app_provider_complete_install() or e.g. 
g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler 
handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned.
+   *
+   * Returns: %G_DBUS_METHOD_INVOCATION_HANDLED or %TRUE if the invocation was handled, 
%G_DBUS_METHOD_INVOCATION_UNHANDLED or %FALSE to let other signal handlers run.
+   */
+  g_signal_new ("handle-install",
+    G_TYPE_FROM_INTERFACE (iface),
+    G_SIGNAL_RUN_LAST,
+    G_STRUCT_OFFSET (GsEphyWebAppProviderIface, handle_install),
+    g_signal_accumulator_true_handled,
+    NULL,
+    g_cclosure_marshal_generic,
+    G_TYPE_BOOLEAN,
+    4,
+    G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
+
+  /**
+   * GsEphyWebAppProvider::handle-uninstall:
+   * @object: A #GsEphyWebAppProvider.
+   * @invocation: A #GDBusMethodInvocation.
+   * @arg_desktop_path: Argument passed by remote caller.
+   *
+   * Signal emitted when a remote caller is invoking the <link 
linkend="gdbus-method-org-gnome-Epiphany-WebAppProvider.Uninstall">Uninstall()</link> D-Bus method.
+   *
+   * If a signal handler returns %TRUE, it means the signal handler will handle the invocation (e.g. take a 
reference to @invocation and eventually call gs_ephy_web_app_provider_complete_uninstall() or e.g. 
g_dbus_method_invocation_return_error() on it) and no order signal handlers will run. If no signal handler 
handles the invocation, the %G_DBUS_ERROR_UNKNOWN_METHOD error is returned.
+   *
+   * Returns: %G_DBUS_METHOD_INVOCATION_HANDLED or %TRUE if the invocation was handled, 
%G_DBUS_METHOD_INVOCATION_UNHANDLED or %FALSE to let other signal handlers run.
+   */
+  g_signal_new ("handle-uninstall",
+    G_TYPE_FROM_INTERFACE (iface),
+    G_SIGNAL_RUN_LAST,
+    G_STRUCT_OFFSET (GsEphyWebAppProviderIface, handle_uninstall),
+    g_signal_accumulator_true_handled,
+    NULL,
+    g_cclosure_marshal_generic,
+    G_TYPE_BOOLEAN,
+    2,
+    G_TYPE_DBUS_METHOD_INVOCATION, G_TYPE_STRING);
+
+  /* GObject properties for D-Bus properties: */
+  /**
+   * GsEphyWebAppProvider:version:
+   *
+   * Represents the D-Bus property <link 
linkend="gdbus-property-org-gnome-Epiphany-WebAppProvider.version">"version"</link>.
+   *
+   * Since the D-Bus property for this #GObject property is readable but not writable, it is meaningful to 
read from it on both the client- and service-side. It is only meaningful, however, to write to it on the 
service-side.
+   */
+  g_object_interface_install_property (iface,
+    g_param_spec_uint ("version", "version", "version", 0, G_MAXUINT32, 0, G_PARAM_READWRITE | 
G_PARAM_STATIC_STRINGS));
+}
+
+/**
+ * gs_ephy_web_app_provider_get_version: (skip)
+ * @object: A #GsEphyWebAppProvider.
+ *
+ * Gets the value of the <link 
linkend="gdbus-property-org-gnome-Epiphany-WebAppProvider.version">"version"</link> D-Bus property.
+ *
+ * Since this D-Bus property is readable, it is meaningful to use this function on both the client- and 
service-side.
+ *
+ * Returns: The property value.
+ */
+guint 
+gs_ephy_web_app_provider_get_version (GsEphyWebAppProvider *object)
+{
+  return GS_EPHY_WEB_APP_PROVIDER_GET_IFACE (object)->get_version (object);
+}
+
+/**
+ * gs_ephy_web_app_provider_set_version: (skip)
+ * @object: A #GsEphyWebAppProvider.
+ * @value: The value to set.
+ *
+ * Sets the <link linkend="gdbus-property-org-gnome-Epiphany-WebAppProvider.version">"version"</link> D-Bus 
property to @value.
+ *
+ * Since this D-Bus property is not writable, it is only meaningful to use this function on the service-side.
+ */
+void
+gs_ephy_web_app_provider_set_version (GsEphyWebAppProvider *object, guint value)
+{
+  g_object_set (G_OBJECT (object), "version", value, NULL);
+}
+
+/**
+ * gs_ephy_web_app_provider_call_get_installed_apps:
+ * @proxy: A #GsEphyWebAppProviderProxy.
+ * @cancellable: (nullable): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
+ * @user_data: User data to pass to @callback.
+ *
+ * Asynchronously invokes the <link 
linkend="gdbus-method-org-gnome-Epiphany-WebAppProvider.GetInstalledApps">GetInstalledApps()</link> D-Bus 
method on @proxy.
+ * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread 
you are calling this method from (see g_main_context_push_thread_default()).
+ * You can then call gs_ephy_web_app_provider_call_get_installed_apps_finish() to get the result of the 
operation.
+ *
+ * See gs_ephy_web_app_provider_call_get_installed_apps_sync() for the synchronous, blocking version of this 
method.
+ */
+void
+gs_ephy_web_app_provider_call_get_installed_apps (
+    GsEphyWebAppProvider *proxy,
+    GCancellable *cancellable,
+    GAsyncReadyCallback callback,
+    gpointer user_data)
+{
+  g_dbus_proxy_call (G_DBUS_PROXY (proxy),
+    "GetInstalledApps",
+    g_variant_new ("()"),
+    G_DBUS_CALL_FLAGS_NONE,
+    -1,
+    cancellable,
+    callback,
+    user_data);
+}
+
+/**
+ * gs_ephy_web_app_provider_call_get_installed_apps_finish:
+ * @proxy: A #GsEphyWebAppProviderProxy.
+ * @out_webapps: (out) (optional) (array zero-terminated=1): Return location for return parameter or %NULL 
to ignore.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to 
gs_ephy_web_app_provider_call_get_installed_apps().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with gs_ephy_web_app_provider_call_get_installed_apps().
+ *
+ * Returns: (skip): %TRUE if the call succeeded, %FALSE if @error is set.
+ */
+gboolean
+gs_ephy_web_app_provider_call_get_installed_apps_finish (
+    GsEphyWebAppProvider *proxy,
+    gchar ***out_webapps,
+    GAsyncResult *res,
+    GError **error)
+{
+  GVariant *_ret;
+  _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error);
+  if (_ret == NULL)
+    goto _out;
+  g_variant_get (_ret,
+                 "(^as)",
+                 out_webapps);
+  g_variant_unref (_ret);
+_out:
+  return _ret != NULL;
+}
+
+/**
+ * gs_ephy_web_app_provider_call_get_installed_apps_sync:
+ * @proxy: A #GsEphyWebAppProviderProxy.
+ * @out_webapps: (out) (optional) (array zero-terminated=1): Return location for return parameter or %NULL 
to ignore.
+ * @cancellable: (nullable): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously invokes the <link 
linkend="gdbus-method-org-gnome-Epiphany-WebAppProvider.GetInstalledApps">GetInstalledApps()</link> D-Bus 
method on @proxy. The calling thread is blocked until a reply is received.
+ *
+ * See gs_ephy_web_app_provider_call_get_installed_apps() for the asynchronous version of this method.
+ *
+ * Returns: (skip): %TRUE if the call succeeded, %FALSE if @error is set.
+ */
+gboolean
+gs_ephy_web_app_provider_call_get_installed_apps_sync (
+    GsEphyWebAppProvider *proxy,
+    gchar ***out_webapps,
+    GCancellable *cancellable,
+    GError **error)
+{
+  GVariant *_ret;
+  _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy),
+    "GetInstalledApps",
+    g_variant_new ("()"),
+    G_DBUS_CALL_FLAGS_NONE,
+    -1,
+    cancellable,
+    error);
+  if (_ret == NULL)
+    goto _out;
+  g_variant_get (_ret,
+                 "(^as)",
+                 out_webapps);
+  g_variant_unref (_ret);
+_out:
+  return _ret != NULL;
+}
+
+/**
+ * gs_ephy_web_app_provider_call_install:
+ * @proxy: A #GsEphyWebAppProviderProxy.
+ * @arg_url: Argument to pass with the method invocation.
+ * @arg_name: Argument to pass with the method invocation.
+ * @arg_install_token: Argument to pass with the method invocation.
+ * @cancellable: (nullable): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
+ * @user_data: User data to pass to @callback.
+ *
+ * Asynchronously invokes the <link 
linkend="gdbus-method-org-gnome-Epiphany-WebAppProvider.Install">Install()</link> D-Bus method on @proxy.
+ * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread 
you are calling this method from (see g_main_context_push_thread_default()).
+ * You can then call gs_ephy_web_app_provider_call_install_finish() to get the result of the operation.
+ *
+ * See gs_ephy_web_app_provider_call_install_sync() for the synchronous, blocking version of this method.
+ */
+void
+gs_ephy_web_app_provider_call_install (
+    GsEphyWebAppProvider *proxy,
+    const gchar *arg_url,
+    const gchar *arg_name,
+    const gchar *arg_install_token,
+    GCancellable *cancellable,
+    GAsyncReadyCallback callback,
+    gpointer user_data)
+{
+  g_dbus_proxy_call (G_DBUS_PROXY (proxy),
+    "Install",
+    g_variant_new ("(sss)",
+                   arg_url,
+                   arg_name,
+                   arg_install_token),
+    G_DBUS_CALL_FLAGS_NONE,
+    -1,
+    cancellable,
+    callback,
+    user_data);
+}
+
+/**
+ * gs_ephy_web_app_provider_call_install_finish:
+ * @proxy: A #GsEphyWebAppProviderProxy.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to 
gs_ephy_web_app_provider_call_install().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with gs_ephy_web_app_provider_call_install().
+ *
+ * Returns: (skip): %TRUE if the call succeeded, %FALSE if @error is set.
+ */
+gboolean
+gs_ephy_web_app_provider_call_install_finish (
+    GsEphyWebAppProvider *proxy,
+    GAsyncResult *res,
+    GError **error)
+{
+  GVariant *_ret;
+  _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error);
+  if (_ret == NULL)
+    goto _out;
+  g_variant_get (_ret,
+                 "()");
+  g_variant_unref (_ret);
+_out:
+  return _ret != NULL;
+}
+
+/**
+ * gs_ephy_web_app_provider_call_install_sync:
+ * @proxy: A #GsEphyWebAppProviderProxy.
+ * @arg_url: Argument to pass with the method invocation.
+ * @arg_name: Argument to pass with the method invocation.
+ * @arg_install_token: Argument to pass with the method invocation.
+ * @cancellable: (nullable): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously invokes the <link 
linkend="gdbus-method-org-gnome-Epiphany-WebAppProvider.Install">Install()</link> D-Bus method on @proxy. The 
calling thread is blocked until a reply is received.
+ *
+ * See gs_ephy_web_app_provider_call_install() for the asynchronous version of this method.
+ *
+ * Returns: (skip): %TRUE if the call succeeded, %FALSE if @error is set.
+ */
+gboolean
+gs_ephy_web_app_provider_call_install_sync (
+    GsEphyWebAppProvider *proxy,
+    const gchar *arg_url,
+    const gchar *arg_name,
+    const gchar *arg_install_token,
+    GCancellable *cancellable,
+    GError **error)
+{
+  GVariant *_ret;
+  _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy),
+    "Install",
+    g_variant_new ("(sss)",
+                   arg_url,
+                   arg_name,
+                   arg_install_token),
+    G_DBUS_CALL_FLAGS_NONE,
+    -1,
+    cancellable,
+    error);
+  if (_ret == NULL)
+    goto _out;
+  g_variant_get (_ret,
+                 "()");
+  g_variant_unref (_ret);
+_out:
+  return _ret != NULL;
+}
+
+/**
+ * gs_ephy_web_app_provider_call_uninstall:
+ * @proxy: A #GsEphyWebAppProviderProxy.
+ * @arg_desktop_path: Argument to pass with the method invocation.
+ * @cancellable: (nullable): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL.
+ * @user_data: User data to pass to @callback.
+ *
+ * Asynchronously invokes the <link 
linkend="gdbus-method-org-gnome-Epiphany-WebAppProvider.Uninstall">Uninstall()</link> D-Bus method on @proxy.
+ * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread 
you are calling this method from (see g_main_context_push_thread_default()).
+ * You can then call gs_ephy_web_app_provider_call_uninstall_finish() to get the result of the operation.
+ *
+ * See gs_ephy_web_app_provider_call_uninstall_sync() for the synchronous, blocking version of this method.
+ */
+void
+gs_ephy_web_app_provider_call_uninstall (
+    GsEphyWebAppProvider *proxy,
+    const gchar *arg_desktop_path,
+    GCancellable *cancellable,
+    GAsyncReadyCallback callback,
+    gpointer user_data)
+{
+  g_dbus_proxy_call (G_DBUS_PROXY (proxy),
+    "Uninstall",
+    g_variant_new ("(s)",
+                   arg_desktop_path),
+    G_DBUS_CALL_FLAGS_NONE,
+    -1,
+    cancellable,
+    callback,
+    user_data);
+}
+
+/**
+ * gs_ephy_web_app_provider_call_uninstall_finish:
+ * @proxy: A #GsEphyWebAppProviderProxy.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to 
gs_ephy_web_app_provider_call_uninstall().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with gs_ephy_web_app_provider_call_uninstall().
+ *
+ * Returns: (skip): %TRUE if the call succeeded, %FALSE if @error is set.
+ */
+gboolean
+gs_ephy_web_app_provider_call_uninstall_finish (
+    GsEphyWebAppProvider *proxy,
+    GAsyncResult *res,
+    GError **error)
+{
+  GVariant *_ret;
+  _ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error);
+  if (_ret == NULL)
+    goto _out;
+  g_variant_get (_ret,
+                 "()");
+  g_variant_unref (_ret);
+_out:
+  return _ret != NULL;
+}
+
+/**
+ * gs_ephy_web_app_provider_call_uninstall_sync:
+ * @proxy: A #GsEphyWebAppProviderProxy.
+ * @arg_desktop_path: Argument to pass with the method invocation.
+ * @cancellable: (nullable): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously invokes the <link 
linkend="gdbus-method-org-gnome-Epiphany-WebAppProvider.Uninstall">Uninstall()</link> D-Bus method on @proxy. 
The calling thread is blocked until a reply is received.
+ *
+ * See gs_ephy_web_app_provider_call_uninstall() for the asynchronous version of this method.
+ *
+ * Returns: (skip): %TRUE if the call succeeded, %FALSE if @error is set.
+ */
+gboolean
+gs_ephy_web_app_provider_call_uninstall_sync (
+    GsEphyWebAppProvider *proxy,
+    const gchar *arg_desktop_path,
+    GCancellable *cancellable,
+    GError **error)
+{
+  GVariant *_ret;
+  _ret = g_dbus_proxy_call_sync (G_DBUS_PROXY (proxy),
+    "Uninstall",
+    g_variant_new ("(s)",
+                   arg_desktop_path),
+    G_DBUS_CALL_FLAGS_NONE,
+    -1,
+    cancellable,
+    error);
+  if (_ret == NULL)
+    goto _out;
+  g_variant_get (_ret,
+                 "()");
+  g_variant_unref (_ret);
+_out:
+  return _ret != NULL;
+}
+
+/**
+ * gs_ephy_web_app_provider_complete_get_installed_apps:
+ * @object: A #GsEphyWebAppProvider.
+ * @invocation: (transfer full): A #GDBusMethodInvocation.
+ * @webapps: Parameter to return.
+ *
+ * Helper function used in service implementations to finish handling invocations of the <link 
linkend="gdbus-method-org-gnome-Epiphany-WebAppProvider.GetInstalledApps">GetInstalledApps()</link> D-Bus 
method. If you instead want to finish handling an invocation by returning an error, use 
g_dbus_method_invocation_return_error() or similar.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+gs_ephy_web_app_provider_complete_get_installed_apps (
+    GsEphyWebAppProvider *object G_GNUC_UNUSED,
+    GDBusMethodInvocation *invocation,
+    const gchar *const *webapps)
+{
+  g_dbus_method_invocation_return_value (invocation,
+    g_variant_new ("(^as)",
+                   webapps));
+}
+
+/**
+ * gs_ephy_web_app_provider_complete_install:
+ * @object: A #GsEphyWebAppProvider.
+ * @invocation: (transfer full): A #GDBusMethodInvocation.
+ *
+ * Helper function used in service implementations to finish handling invocations of the <link 
linkend="gdbus-method-org-gnome-Epiphany-WebAppProvider.Install">Install()</link> D-Bus method. If you 
instead want to finish handling an invocation by returning an error, use 
g_dbus_method_invocation_return_error() or similar.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+gs_ephy_web_app_provider_complete_install (
+    GsEphyWebAppProvider *object G_GNUC_UNUSED,
+    GDBusMethodInvocation *invocation)
+{
+  g_dbus_method_invocation_return_value (invocation,
+    g_variant_new ("()"));
+}
+
+/**
+ * gs_ephy_web_app_provider_complete_uninstall:
+ * @object: A #GsEphyWebAppProvider.
+ * @invocation: (transfer full): A #GDBusMethodInvocation.
+ *
+ * Helper function used in service implementations to finish handling invocations of the <link 
linkend="gdbus-method-org-gnome-Epiphany-WebAppProvider.Uninstall">Uninstall()</link> D-Bus method. If you 
instead want to finish handling an invocation by returning an error, use 
g_dbus_method_invocation_return_error() or similar.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+gs_ephy_web_app_provider_complete_uninstall (
+    GsEphyWebAppProvider *object G_GNUC_UNUSED,
+    GDBusMethodInvocation *invocation)
+{
+  g_dbus_method_invocation_return_value (invocation,
+    g_variant_new ("()"));
+}
+
+/* ------------------------------------------------------------------------ */
+
+/**
+ * GsEphyWebAppProviderProxy:
+ *
+ * The #GsEphyWebAppProviderProxy structure contains only private data and should only be accessed using the 
provided API.
+ */
+
+/**
+ * GsEphyWebAppProviderProxyClass:
+ * @parent_class: The parent class.
+ *
+ * Class structure for #GsEphyWebAppProviderProxy.
+ */
+
+struct _GsEphyWebAppProviderProxyPrivate
+{
+  GData *qdata;
+};
+
+static void gs_ephy_web_app_provider_proxy_iface_init (GsEphyWebAppProviderIface *iface);
+
+#if GLIB_VERSION_MAX_ALLOWED >= GLIB_VERSION_2_38
+G_DEFINE_TYPE_WITH_CODE (GsEphyWebAppProviderProxy, gs_ephy_web_app_provider_proxy, G_TYPE_DBUS_PROXY,
+                         G_ADD_PRIVATE (GsEphyWebAppProviderProxy)
+                         G_IMPLEMENT_INTERFACE (GS_EPHY_TYPE_WEB_APP_PROVIDER, 
gs_ephy_web_app_provider_proxy_iface_init))
+
+#else
+G_DEFINE_TYPE_WITH_CODE (GsEphyWebAppProviderProxy, gs_ephy_web_app_provider_proxy, G_TYPE_DBUS_PROXY,
+                         G_IMPLEMENT_INTERFACE (GS_EPHY_TYPE_WEB_APP_PROVIDER, 
gs_ephy_web_app_provider_proxy_iface_init))
+
+#endif
+static void
+gs_ephy_web_app_provider_proxy_finalize (GObject *object)
+{
+  GsEphyWebAppProviderProxy *proxy = GS_EPHY_WEB_APP_PROVIDER_PROXY (object);
+  g_datalist_clear (&proxy->priv->qdata);
+  G_OBJECT_CLASS (gs_ephy_web_app_provider_proxy_parent_class)->finalize (object);
+}
+
+static void
+gs_ephy_web_app_provider_proxy_get_property (GObject      *object,
+  guint         prop_id,
+  GValue       *value,
+  GParamSpec   *pspec G_GNUC_UNUSED)
+{
+  const _ExtendedGDBusPropertyInfo *info;
+  GVariant *variant;
+  g_assert (prop_id != 0 && prop_id - 1 < 1);
+  info = (const _ExtendedGDBusPropertyInfo *) _gs_ephy_web_app_provider_property_info_pointers[prop_id - 1];
+  variant = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (object), info->parent_struct.name);
+  if (info->use_gvariant)
+    {
+      g_value_set_variant (value, variant);
+    }
+  else
+    {
+      if (variant != NULL)
+        g_dbus_gvariant_to_gvalue (variant, value);
+    }
+  if (variant != NULL)
+    g_variant_unref (variant);
+}
+
+static void
+gs_ephy_web_app_provider_proxy_set_property_cb (GDBusProxy *proxy,
+  GAsyncResult *res,
+  gpointer      user_data)
+{
+  const _ExtendedGDBusPropertyInfo *info = user_data;
+  GError *error;
+  GVariant *_ret;
+  error = NULL;
+  _ret = g_dbus_proxy_call_finish (proxy, res, &error);
+  if (!_ret)
+    {
+      g_warning ("Error setting property '%s' on interface org.gnome.Epiphany.WebAppProvider: %s (%s, %d)",
+                 info->parent_struct.name, 
+                 error->message, g_quark_to_string (error->domain), error->code);
+      g_error_free (error);
+    }
+  else
+    {
+      g_variant_unref (_ret);
+    }
+}
+
+static void
+gs_ephy_web_app_provider_proxy_set_property (GObject      *object,
+  guint         prop_id,
+  const GValue *value,
+  GParamSpec   *pspec G_GNUC_UNUSED)
+{
+  const _ExtendedGDBusPropertyInfo *info;
+  GVariant *variant;
+  g_assert (prop_id != 0 && prop_id - 1 < 1);
+  info = (const _ExtendedGDBusPropertyInfo *) _gs_ephy_web_app_provider_property_info_pointers[prop_id - 1];
+  variant = g_dbus_gvalue_to_gvariant (value, G_VARIANT_TYPE (info->parent_struct.signature));
+  g_dbus_proxy_call (G_DBUS_PROXY (object),
+    "org.freedesktop.DBus.Properties.Set",
+    g_variant_new ("(ssv)", "org.gnome.Epiphany.WebAppProvider", info->parent_struct.name, variant),
+    G_DBUS_CALL_FLAGS_NONE,
+    -1,
+    NULL, (GAsyncReadyCallback) gs_ephy_web_app_provider_proxy_set_property_cb, (GDBusPropertyInfo *) 
&info->parent_struct);
+  g_variant_unref (variant);
+}
+
+static void
+gs_ephy_web_app_provider_proxy_g_signal (GDBusProxy *proxy,
+  const gchar *sender_name G_GNUC_UNUSED,
+  const gchar *signal_name,
+  GVariant *parameters)
+{
+  _ExtendedGDBusSignalInfo *info;
+  GVariantIter iter;
+  GVariant *child;
+  GValue *paramv;
+  gsize num_params;
+  gsize n;
+  guint signal_id;
+  info = (_ExtendedGDBusSignalInfo *) g_dbus_interface_info_lookup_signal ((GDBusInterfaceInfo *) 
&_gs_ephy_web_app_provider_interface_info.parent_struct, signal_name);
+  if (info == NULL)
+    return;
+  num_params = g_variant_n_children (parameters);
+  paramv = g_new0 (GValue, num_params + 1);
+  g_value_init (&paramv[0], GS_EPHY_TYPE_WEB_APP_PROVIDER);
+  g_value_set_object (&paramv[0], proxy);
+  g_variant_iter_init (&iter, parameters);
+  n = 1;
+  while ((child = g_variant_iter_next_value (&iter)) != NULL)
+    {
+      _ExtendedGDBusArgInfo *arg_info = (_ExtendedGDBusArgInfo *) info->parent_struct.args[n - 1];
+      if (arg_info->use_gvariant)
+        {
+          g_value_init (&paramv[n], G_TYPE_VARIANT);
+          g_value_set_variant (&paramv[n], child);
+          n++;
+        }
+      else
+        g_dbus_gvariant_to_gvalue (child, &paramv[n++]);
+      g_variant_unref (child);
+    }
+  signal_id = g_signal_lookup (info->signal_name, GS_EPHY_TYPE_WEB_APP_PROVIDER);
+  g_signal_emitv (paramv, signal_id, 0, NULL);
+  for (n = 0; n < num_params + 1; n++)
+    g_value_unset (&paramv[n]);
+  g_free (paramv);
+}
+
+static void
+gs_ephy_web_app_provider_proxy_g_properties_changed (GDBusProxy *_proxy,
+  GVariant *changed_properties,
+  const gchar *const *invalidated_properties)
+{
+  GsEphyWebAppProviderProxy *proxy = GS_EPHY_WEB_APP_PROVIDER_PROXY (_proxy);
+  guint n;
+  const gchar *key;
+  GVariantIter *iter;
+  _ExtendedGDBusPropertyInfo *info;
+  g_variant_get (changed_properties, "a{sv}", &iter);
+  while (g_variant_iter_next (iter, "{&sv}", &key, NULL))
+    {
+      info = (_ExtendedGDBusPropertyInfo *) g_dbus_interface_info_lookup_property ((GDBusInterfaceInfo *) 
&_gs_ephy_web_app_provider_interface_info.parent_struct, key);
+      g_datalist_remove_data (&proxy->priv->qdata, key);
+      if (info != NULL)
+        g_object_notify (G_OBJECT (proxy), info->hyphen_name);
+    }
+  g_variant_iter_free (iter);
+  for (n = 0; invalidated_properties[n] != NULL; n++)
+    {
+      info = (_ExtendedGDBusPropertyInfo *) g_dbus_interface_info_lookup_property ((GDBusInterfaceInfo *) 
&_gs_ephy_web_app_provider_interface_info.parent_struct, invalidated_properties[n]);
+      g_datalist_remove_data (&proxy->priv->qdata, invalidated_properties[n]);
+      if (info != NULL)
+        g_object_notify (G_OBJECT (proxy), info->hyphen_name);
+    }
+}
+
+static guint 
+gs_ephy_web_app_provider_proxy_get_version (GsEphyWebAppProvider *object)
+{
+  GsEphyWebAppProviderProxy *proxy = GS_EPHY_WEB_APP_PROVIDER_PROXY (object);
+  GVariant *variant;
+  guint value = 0;
+  variant = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), "version");
+  if (variant != NULL)
+    {
+      value = g_variant_get_uint32 (variant);
+      g_variant_unref (variant);
+    }
+  return value;
+}
+
+static void
+gs_ephy_web_app_provider_proxy_init (GsEphyWebAppProviderProxy *proxy)
+{
+#if GLIB_VERSION_MAX_ALLOWED >= GLIB_VERSION_2_38
+  proxy->priv = gs_ephy_web_app_provider_proxy_get_instance_private (proxy);
+#else
+  proxy->priv = G_TYPE_INSTANCE_GET_PRIVATE (proxy, GS_EPHY_TYPE_WEB_APP_PROVIDER_PROXY, 
GsEphyWebAppProviderProxyPrivate);
+#endif
+
+  g_dbus_proxy_set_interface_info (G_DBUS_PROXY (proxy), gs_ephy_web_app_provider_interface_info ());
+}
+
+static void
+gs_ephy_web_app_provider_proxy_class_init (GsEphyWebAppProviderProxyClass *klass)
+{
+  GObjectClass *gobject_class;
+  GDBusProxyClass *proxy_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize     = gs_ephy_web_app_provider_proxy_finalize;
+  gobject_class->get_property = gs_ephy_web_app_provider_proxy_get_property;
+  gobject_class->set_property = gs_ephy_web_app_provider_proxy_set_property;
+
+  proxy_class = G_DBUS_PROXY_CLASS (klass);
+  proxy_class->g_signal = gs_ephy_web_app_provider_proxy_g_signal;
+  proxy_class->g_properties_changed = gs_ephy_web_app_provider_proxy_g_properties_changed;
+
+  gs_ephy_web_app_provider_override_properties (gobject_class, 1);
+
+#if GLIB_VERSION_MAX_ALLOWED < GLIB_VERSION_2_38
+  g_type_class_add_private (klass, sizeof (GsEphyWebAppProviderProxyPrivate));
+#endif
+}
+
+static void
+gs_ephy_web_app_provider_proxy_iface_init (GsEphyWebAppProviderIface *iface)
+{
+  iface->get_version = gs_ephy_web_app_provider_proxy_get_version;
+}
+
+/**
+ * gs_ephy_web_app_provider_proxy_new:
+ * @connection: A #GDBusConnection.
+ * @flags: Flags from the #GDBusProxyFlags enumeration.
+ * @name: (nullable): A bus name (well-known or unique) or %NULL if @connection is not a message bus 
connection.
+ * @object_path: An object path.
+ * @cancellable: (nullable): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
+ * @user_data: User data to pass to @callback.
+ *
+ * Asynchronously creates a proxy for the D-Bus interface <link 
linkend="gdbus-interface-org-gnome-Epiphany-WebAppProvider.top_of_page">org.gnome.Epiphany.WebAppProvider</link>.
 See g_dbus_proxy_new() for more details.
+ *
+ * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread 
you are calling this method from (see g_main_context_push_thread_default()).
+ * You can then call gs_ephy_web_app_provider_proxy_new_finish() to get the result of the operation.
+ *
+ * See gs_ephy_web_app_provider_proxy_new_sync() for the synchronous, blocking version of this constructor.
+ */
+void
+gs_ephy_web_app_provider_proxy_new (
+    GDBusConnection     *connection,
+    GDBusProxyFlags      flags,
+    const gchar         *name,
+    const gchar         *object_path,
+    GCancellable        *cancellable,
+    GAsyncReadyCallback  callback,
+    gpointer             user_data)
+{
+  g_async_initable_new_async (GS_EPHY_TYPE_WEB_APP_PROVIDER_PROXY, G_PRIORITY_DEFAULT, cancellable, 
callback, user_data, "g-flags", flags, "g-name", name, "g-connection", connection, "g-object-path", 
object_path, "g-interface-name", "org.gnome.Epiphany.WebAppProvider", NULL);
+}
+
+/**
+ * gs_ephy_web_app_provider_proxy_new_finish:
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to 
gs_ephy_web_app_provider_proxy_new().
+ * @error: Return location for error or %NULL
+ *
+ * Finishes an operation started with gs_ephy_web_app_provider_proxy_new().
+ *
+ * Returns: (transfer full) (type GsEphyWebAppProviderProxy): The constructed proxy object or %NULL if 
@error is set.
+ */
+GsEphyWebAppProvider *
+gs_ephy_web_app_provider_proxy_new_finish (
+    GAsyncResult        *res,
+    GError             **error)
+{
+  GObject *ret;
+  GObject *source_object;
+  source_object = g_async_result_get_source_object (res);
+  ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error);
+  g_object_unref (source_object);
+  if (ret != NULL)
+    return GS_EPHY_WEB_APP_PROVIDER (ret);
+  else
+    return NULL;
+}
+
+/**
+ * gs_ephy_web_app_provider_proxy_new_sync:
+ * @connection: A #GDBusConnection.
+ * @flags: Flags from the #GDBusProxyFlags enumeration.
+ * @name: (nullable): A bus name (well-known or unique) or %NULL if @connection is not a message bus 
connection.
+ * @object_path: An object path.
+ * @cancellable: (nullable): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL
+ *
+ * Synchronously creates a proxy for the D-Bus interface <link 
linkend="gdbus-interface-org-gnome-Epiphany-WebAppProvider.top_of_page">org.gnome.Epiphany.WebAppProvider</link>.
 See g_dbus_proxy_new_sync() for more details.
+ *
+ * The calling thread is blocked until a reply is received.
+ *
+ * See gs_ephy_web_app_provider_proxy_new() for the asynchronous version of this constructor.
+ *
+ * Returns: (transfer full) (type GsEphyWebAppProviderProxy): The constructed proxy object or %NULL if 
@error is set.
+ */
+GsEphyWebAppProvider *
+gs_ephy_web_app_provider_proxy_new_sync (
+    GDBusConnection     *connection,
+    GDBusProxyFlags      flags,
+    const gchar         *name,
+    const gchar         *object_path,
+    GCancellable        *cancellable,
+    GError             **error)
+{
+  GInitable *ret;
+  ret = g_initable_new (GS_EPHY_TYPE_WEB_APP_PROVIDER_PROXY, cancellable, error, "g-flags", flags, "g-name", 
name, "g-connection", connection, "g-object-path", object_path, "g-interface-name", 
"org.gnome.Epiphany.WebAppProvider", NULL);
+  if (ret != NULL)
+    return GS_EPHY_WEB_APP_PROVIDER (ret);
+  else
+    return NULL;
+}
+
+
+/**
+ * gs_ephy_web_app_provider_proxy_new_for_bus:
+ * @bus_type: A #GBusType.
+ * @flags: Flags from the #GDBusProxyFlags enumeration.
+ * @name: A bus name (well-known or unique).
+ * @object_path: An object path.
+ * @cancellable: (nullable): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
+ * @user_data: User data to pass to @callback.
+ *
+ * Like gs_ephy_web_app_provider_proxy_new() but takes a #GBusType instead of a #GDBusConnection.
+ *
+ * When the operation is finished, @callback will be invoked in the thread-default main loop of the thread 
you are calling this method from (see g_main_context_push_thread_default()).
+ * You can then call gs_ephy_web_app_provider_proxy_new_for_bus_finish() to get the result of the operation.
+ *
+ * See gs_ephy_web_app_provider_proxy_new_for_bus_sync() for the synchronous, blocking version of this 
constructor.
+ */
+void
+gs_ephy_web_app_provider_proxy_new_for_bus (
+    GBusType             bus_type,
+    GDBusProxyFlags      flags,
+    const gchar         *name,
+    const gchar         *object_path,
+    GCancellable        *cancellable,
+    GAsyncReadyCallback  callback,
+    gpointer             user_data)
+{
+  g_async_initable_new_async (GS_EPHY_TYPE_WEB_APP_PROVIDER_PROXY, G_PRIORITY_DEFAULT, cancellable, 
callback, user_data, "g-flags", flags, "g-name", name, "g-bus-type", bus_type, "g-object-path", object_path, 
"g-interface-name", "org.gnome.Epiphany.WebAppProvider", NULL);
+}
+
+/**
+ * gs_ephy_web_app_provider_proxy_new_for_bus_finish:
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to 
gs_ephy_web_app_provider_proxy_new_for_bus().
+ * @error: Return location for error or %NULL
+ *
+ * Finishes an operation started with gs_ephy_web_app_provider_proxy_new_for_bus().
+ *
+ * Returns: (transfer full) (type GsEphyWebAppProviderProxy): The constructed proxy object or %NULL if 
@error is set.
+ */
+GsEphyWebAppProvider *
+gs_ephy_web_app_provider_proxy_new_for_bus_finish (
+    GAsyncResult        *res,
+    GError             **error)
+{
+  GObject *ret;
+  GObject *source_object;
+  source_object = g_async_result_get_source_object (res);
+  ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error);
+  g_object_unref (source_object);
+  if (ret != NULL)
+    return GS_EPHY_WEB_APP_PROVIDER (ret);
+  else
+    return NULL;
+}
+
+/**
+ * gs_ephy_web_app_provider_proxy_new_for_bus_sync:
+ * @bus_type: A #GBusType.
+ * @flags: Flags from the #GDBusProxyFlags enumeration.
+ * @name: A bus name (well-known or unique).
+ * @object_path: An object path.
+ * @cancellable: (nullable): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL
+ *
+ * Like gs_ephy_web_app_provider_proxy_new_sync() but takes a #GBusType instead of a #GDBusConnection.
+ *
+ * The calling thread is blocked until a reply is received.
+ *
+ * See gs_ephy_web_app_provider_proxy_new_for_bus() for the asynchronous version of this constructor.
+ *
+ * Returns: (transfer full) (type GsEphyWebAppProviderProxy): The constructed proxy object or %NULL if 
@error is set.
+ */
+GsEphyWebAppProvider *
+gs_ephy_web_app_provider_proxy_new_for_bus_sync (
+    GBusType             bus_type,
+    GDBusProxyFlags      flags,
+    const gchar         *name,
+    const gchar         *object_path,
+    GCancellable        *cancellable,
+    GError             **error)
+{
+  GInitable *ret;
+  ret = g_initable_new (GS_EPHY_TYPE_WEB_APP_PROVIDER_PROXY, cancellable, error, "g-flags", flags, "g-name", 
name, "g-bus-type", bus_type, "g-object-path", object_path, "g-interface-name", 
"org.gnome.Epiphany.WebAppProvider", NULL);
+  if (ret != NULL)
+    return GS_EPHY_WEB_APP_PROVIDER (ret);
+  else
+    return NULL;
+}
+
+
+/* ------------------------------------------------------------------------ */
+
+/**
+ * GsEphyWebAppProviderSkeleton:
+ *
+ * The #GsEphyWebAppProviderSkeleton structure contains only private data and should only be accessed using 
the provided API.
+ */
+
+/**
+ * GsEphyWebAppProviderSkeletonClass:
+ * @parent_class: The parent class.
+ *
+ * Class structure for #GsEphyWebAppProviderSkeleton.
+ */
+
+struct _GsEphyWebAppProviderSkeletonPrivate
+{
+  GValue *properties;
+  GList *changed_properties;
+  GSource *changed_properties_idle_source;
+  GMainContext *context;
+  GMutex lock;
+};
+
+static void
+_gs_ephy_web_app_provider_skeleton_handle_method_call (
+  GDBusConnection *connection G_GNUC_UNUSED,
+  const gchar *sender G_GNUC_UNUSED,
+  const gchar *object_path G_GNUC_UNUSED,
+  const gchar *interface_name,
+  const gchar *method_name,
+  GVariant *parameters,
+  GDBusMethodInvocation *invocation,
+  gpointer user_data)
+{
+  GsEphyWebAppProviderSkeleton *skeleton = GS_EPHY_WEB_APP_PROVIDER_SKELETON (user_data);
+  _ExtendedGDBusMethodInfo *info;
+  GVariantIter iter;
+  GVariant *child;
+  GValue *paramv;
+  gsize num_params;
+  guint num_extra;
+  gsize n;
+  guint signal_id;
+  GValue return_value = G_VALUE_INIT;
+  info = (_ExtendedGDBusMethodInfo *) g_dbus_method_invocation_get_method_info (invocation);
+  g_assert (info != NULL);
+  num_params = g_variant_n_children (parameters);
+  num_extra = info->pass_fdlist ? 3 : 2;  paramv = g_new0 (GValue, num_params + num_extra);
+  n = 0;
+  g_value_init (&paramv[n], GS_EPHY_TYPE_WEB_APP_PROVIDER);
+  g_value_set_object (&paramv[n++], skeleton);
+  g_value_init (&paramv[n], G_TYPE_DBUS_METHOD_INVOCATION);
+  g_value_set_object (&paramv[n++], invocation);
+  if (info->pass_fdlist)
+    {
+#ifdef G_OS_UNIX
+      g_value_init (&paramv[n], G_TYPE_UNIX_FD_LIST);
+      g_value_set_object (&paramv[n++], g_dbus_message_get_unix_fd_list 
(g_dbus_method_invocation_get_message (invocation)));
+#else
+      g_assert_not_reached ();
+#endif
+    }
+  g_variant_iter_init (&iter, parameters);
+  while ((child = g_variant_iter_next_value (&iter)) != NULL)
+    {
+      _ExtendedGDBusArgInfo *arg_info = (_ExtendedGDBusArgInfo *) info->parent_struct.in_args[n - num_extra];
+      if (arg_info->use_gvariant)
+        {
+          g_value_init (&paramv[n], G_TYPE_VARIANT);
+          g_value_set_variant (&paramv[n], child);
+          n++;
+        }
+      else
+        g_dbus_gvariant_to_gvalue (child, &paramv[n++]);
+      g_variant_unref (child);
+    }
+  signal_id = g_signal_lookup (info->signal_name, GS_EPHY_TYPE_WEB_APP_PROVIDER);
+  g_value_init (&return_value, G_TYPE_BOOLEAN);
+  g_signal_emitv (paramv, signal_id, 0, &return_value);
+  if (!g_value_get_boolean (&return_value))
+    g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD, "Method %s 
is not implemented on interface %s", method_name, interface_name);
+  g_value_unset (&return_value);
+  for (n = 0; n < num_params + num_extra; n++)
+    g_value_unset (&paramv[n]);
+  g_free (paramv);
+}
+
+static GVariant *
+_gs_ephy_web_app_provider_skeleton_handle_get_property (
+  GDBusConnection *connection G_GNUC_UNUSED,
+  const gchar *sender G_GNUC_UNUSED,
+  const gchar *object_path G_GNUC_UNUSED,
+  const gchar *interface_name G_GNUC_UNUSED,
+  const gchar *property_name,
+  GError **error,
+  gpointer user_data)
+{
+  GsEphyWebAppProviderSkeleton *skeleton = GS_EPHY_WEB_APP_PROVIDER_SKELETON (user_data);
+  GValue value = G_VALUE_INIT;
+  GParamSpec *pspec;
+  _ExtendedGDBusPropertyInfo *info;
+  GVariant *ret;
+  ret = NULL;
+  info = (_ExtendedGDBusPropertyInfo *) g_dbus_interface_info_lookup_property ((GDBusInterfaceInfo *) 
&_gs_ephy_web_app_provider_interface_info.parent_struct, property_name);
+  g_assert (info != NULL);
+  pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (skeleton), info->hyphen_name);
+  if (pspec == NULL)
+    {
+      g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No property with name %s", 
property_name);
+    }
+  else
+    {
+      g_value_init (&value, pspec->value_type);
+      g_object_get_property (G_OBJECT (skeleton), info->hyphen_name, &value);
+      ret = g_dbus_gvalue_to_gvariant (&value, G_VARIANT_TYPE (info->parent_struct.signature));
+      g_value_unset (&value);
+    }
+  return ret;
+}
+
+static gboolean
+_gs_ephy_web_app_provider_skeleton_handle_set_property (
+  GDBusConnection *connection G_GNUC_UNUSED,
+  const gchar *sender G_GNUC_UNUSED,
+  const gchar *object_path G_GNUC_UNUSED,
+  const gchar *interface_name G_GNUC_UNUSED,
+  const gchar *property_name,
+  GVariant *variant,
+  GError **error,
+  gpointer user_data)
+{
+  GsEphyWebAppProviderSkeleton *skeleton = GS_EPHY_WEB_APP_PROVIDER_SKELETON (user_data);
+  GValue value = G_VALUE_INIT;
+  GParamSpec *pspec;
+  _ExtendedGDBusPropertyInfo *info;
+  gboolean ret;
+  ret = FALSE;
+  info = (_ExtendedGDBusPropertyInfo *) g_dbus_interface_info_lookup_property ((GDBusInterfaceInfo *) 
&_gs_ephy_web_app_provider_interface_info.parent_struct, property_name);
+  g_assert (info != NULL);
+  pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (skeleton), info->hyphen_name);
+  if (pspec == NULL)
+    {
+      g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "No property with name %s", 
property_name);
+    }
+  else
+    {
+      if (info->use_gvariant)
+        g_value_set_variant (&value, variant);
+      else
+        g_dbus_gvariant_to_gvalue (variant, &value);
+      g_object_set_property (G_OBJECT (skeleton), info->hyphen_name, &value);
+      g_value_unset (&value);
+      ret = TRUE;
+    }
+  return ret;
+}
+
+static const GDBusInterfaceVTable _gs_ephy_web_app_provider_skeleton_vtable =
+{
+  _gs_ephy_web_app_provider_skeleton_handle_method_call,
+  _gs_ephy_web_app_provider_skeleton_handle_get_property,
+  _gs_ephy_web_app_provider_skeleton_handle_set_property,
+  {NULL}
+};
+
+static GDBusInterfaceInfo *
+gs_ephy_web_app_provider_skeleton_dbus_interface_get_info (GDBusInterfaceSkeleton *skeleton G_GNUC_UNUSED)
+{
+  return gs_ephy_web_app_provider_interface_info ();
+}
+
+static GDBusInterfaceVTable *
+gs_ephy_web_app_provider_skeleton_dbus_interface_get_vtable (GDBusInterfaceSkeleton *skeleton G_GNUC_UNUSED)
+{
+  return (GDBusInterfaceVTable *) &_gs_ephy_web_app_provider_skeleton_vtable;
+}
+
+static GVariant *
+gs_ephy_web_app_provider_skeleton_dbus_interface_get_properties (GDBusInterfaceSkeleton *_skeleton)
+{
+  GsEphyWebAppProviderSkeleton *skeleton = GS_EPHY_WEB_APP_PROVIDER_SKELETON (_skeleton);
+
+  GVariantBuilder builder;
+  guint n;
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+  if (_gs_ephy_web_app_provider_interface_info.parent_struct.properties == NULL)
+    goto out;
+  for (n = 0; _gs_ephy_web_app_provider_interface_info.parent_struct.properties[n] != NULL; n++)
+    {
+      GDBusPropertyInfo *info = _gs_ephy_web_app_provider_interface_info.parent_struct.properties[n];
+      if (info->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE)
+        {
+          GVariant *value;
+          value = _gs_ephy_web_app_provider_skeleton_handle_get_property 
(g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (skeleton)), NULL, 
g_dbus_interface_skeleton_get_object_path (G_DBUS_INTERFACE_SKELETON (skeleton)), 
"org.gnome.Epiphany.WebAppProvider", info->name, NULL, skeleton);
+          if (value != NULL)
+            {
+              g_variant_take_ref (value);
+              g_variant_builder_add (&builder, "{sv}", info->name, value);
+              g_variant_unref (value);
+            }
+        }
+    }
+out:
+  return g_variant_builder_end (&builder);
+}
+
+static gboolean _gs_ephy_web_app_provider_emit_changed (gpointer user_data);
+
+static void
+gs_ephy_web_app_provider_skeleton_dbus_interface_flush (GDBusInterfaceSkeleton *_skeleton)
+{
+  GsEphyWebAppProviderSkeleton *skeleton = GS_EPHY_WEB_APP_PROVIDER_SKELETON (_skeleton);
+  gboolean emit_changed = FALSE;
+
+  g_mutex_lock (&skeleton->priv->lock);
+  if (skeleton->priv->changed_properties_idle_source != NULL)
+    {
+      g_source_destroy (skeleton->priv->changed_properties_idle_source);
+      skeleton->priv->changed_properties_idle_source = NULL;
+      emit_changed = TRUE;
+    }
+  g_mutex_unlock (&skeleton->priv->lock);
+
+  if (emit_changed)
+    _gs_ephy_web_app_provider_emit_changed (skeleton);
+}
+
+static void gs_ephy_web_app_provider_skeleton_iface_init (GsEphyWebAppProviderIface *iface);
+#if GLIB_VERSION_MAX_ALLOWED >= GLIB_VERSION_2_38
+G_DEFINE_TYPE_WITH_CODE (GsEphyWebAppProviderSkeleton, gs_ephy_web_app_provider_skeleton, 
G_TYPE_DBUS_INTERFACE_SKELETON,
+                         G_ADD_PRIVATE (GsEphyWebAppProviderSkeleton)
+                         G_IMPLEMENT_INTERFACE (GS_EPHY_TYPE_WEB_APP_PROVIDER, 
gs_ephy_web_app_provider_skeleton_iface_init))
+
+#else
+G_DEFINE_TYPE_WITH_CODE (GsEphyWebAppProviderSkeleton, gs_ephy_web_app_provider_skeleton, 
G_TYPE_DBUS_INTERFACE_SKELETON,
+                         G_IMPLEMENT_INTERFACE (GS_EPHY_TYPE_WEB_APP_PROVIDER, 
gs_ephy_web_app_provider_skeleton_iface_init))
+
+#endif
+static void
+gs_ephy_web_app_provider_skeleton_finalize (GObject *object)
+{
+  GsEphyWebAppProviderSkeleton *skeleton = GS_EPHY_WEB_APP_PROVIDER_SKELETON (object);
+  guint n;
+  for (n = 0; n < 1; n++)
+    g_value_unset (&skeleton->priv->properties[n]);
+  g_free (skeleton->priv->properties);
+  g_list_free_full (skeleton->priv->changed_properties, (GDestroyNotify) _changed_property_free);
+  if (skeleton->priv->changed_properties_idle_source != NULL)
+    g_source_destroy (skeleton->priv->changed_properties_idle_source);
+  g_main_context_unref (skeleton->priv->context);
+  g_mutex_clear (&skeleton->priv->lock);
+  G_OBJECT_CLASS (gs_ephy_web_app_provider_skeleton_parent_class)->finalize (object);
+}
+
+static void
+gs_ephy_web_app_provider_skeleton_get_property (GObject      *object,
+  guint         prop_id,
+  GValue       *value,
+  GParamSpec   *pspec G_GNUC_UNUSED)
+{
+  GsEphyWebAppProviderSkeleton *skeleton = GS_EPHY_WEB_APP_PROVIDER_SKELETON (object);
+  g_assert (prop_id != 0 && prop_id - 1 < 1);
+  g_mutex_lock (&skeleton->priv->lock);
+  g_value_copy (&skeleton->priv->properties[prop_id - 1], value);
+  g_mutex_unlock (&skeleton->priv->lock);
+}
+
+static gboolean
+_gs_ephy_web_app_provider_emit_changed (gpointer user_data)
+{
+  GsEphyWebAppProviderSkeleton *skeleton = GS_EPHY_WEB_APP_PROVIDER_SKELETON (user_data);
+  GList *l;
+  GVariantBuilder builder;
+  GVariantBuilder invalidated_builder;
+  guint num_changes;
+
+  g_mutex_lock (&skeleton->priv->lock);
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+  g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
+  for (l = skeleton->priv->changed_properties, num_changes = 0; l != NULL; l = l->next)
+    {
+      ChangedProperty *cp = l->data;
+      GVariant *variant;
+      const GValue *cur_value;
+
+      cur_value = &skeleton->priv->properties[cp->prop_id - 1];
+      if (!_g_value_equal (cur_value, &cp->orig_value))
+        {
+          variant = g_dbus_gvalue_to_gvariant (cur_value, G_VARIANT_TYPE 
(cp->info->parent_struct.signature));
+          g_variant_builder_add (&builder, "{sv}", cp->info->parent_struct.name, variant);
+          g_variant_unref (variant);
+          num_changes++;
+        }
+    }
+  if (num_changes > 0)
+    {
+      GList *connections, *ll;
+      GVariant *signal_variant;
+      signal_variant = g_variant_ref_sink (g_variant_new ("(sa{sv}as)", "org.gnome.Epiphany.WebAppProvider",
+                                           &builder, &invalidated_builder));
+      connections = g_dbus_interface_skeleton_get_connections (G_DBUS_INTERFACE_SKELETON (skeleton));
+      for (ll = connections; ll != NULL; ll = ll->next)
+        {
+          GDBusConnection *connection = ll->data;
+
+          g_dbus_connection_emit_signal (connection,
+                                         NULL, g_dbus_interface_skeleton_get_object_path 
(G_DBUS_INTERFACE_SKELETON (skeleton)),
+                                         "org.freedesktop.DBus.Properties",
+                                         "PropertiesChanged",
+                                         signal_variant,
+                                         NULL);
+        }
+      g_variant_unref (signal_variant);
+      g_list_free_full (connections, g_object_unref);
+    }
+  else
+    {
+      g_variant_builder_clear (&builder);
+      g_variant_builder_clear (&invalidated_builder);
+    }
+  g_list_free_full (skeleton->priv->changed_properties, (GDestroyNotify) _changed_property_free);
+  skeleton->priv->changed_properties = NULL;
+  skeleton->priv->changed_properties_idle_source = NULL;
+  g_mutex_unlock (&skeleton->priv->lock);
+  return FALSE;
+}
+
+static void
+_gs_ephy_web_app_provider_schedule_emit_changed (GsEphyWebAppProviderSkeleton *skeleton, const 
_ExtendedGDBusPropertyInfo *info, guint prop_id, const GValue *orig_value)
+{
+  ChangedProperty *cp;
+  GList *l;
+  cp = NULL;
+  for (l = skeleton->priv->changed_properties; l != NULL; l = l->next)
+    {
+      ChangedProperty *i_cp = l->data;
+      if (i_cp->info == info)
+        {
+          cp = i_cp;
+          break;
+        }
+    }
+  if (cp == NULL)
+    {
+      cp = g_new0 (ChangedProperty, 1);
+      cp->prop_id = prop_id;
+      cp->info = info;
+      skeleton->priv->changed_properties = g_list_prepend (skeleton->priv->changed_properties, cp);
+      g_value_init (&cp->orig_value, G_VALUE_TYPE (orig_value));
+      g_value_copy (orig_value, &cp->orig_value);
+    }
+}
+
+static void
+gs_ephy_web_app_provider_skeleton_notify (GObject      *object,
+  GParamSpec *pspec G_GNUC_UNUSED)
+{
+  GsEphyWebAppProviderSkeleton *skeleton = GS_EPHY_WEB_APP_PROVIDER_SKELETON (object);
+  g_mutex_lock (&skeleton->priv->lock);
+  if (skeleton->priv->changed_properties != NULL &&
+      skeleton->priv->changed_properties_idle_source == NULL)
+    {
+      skeleton->priv->changed_properties_idle_source = g_idle_source_new ();
+      g_source_set_priority (skeleton->priv->changed_properties_idle_source, G_PRIORITY_DEFAULT);
+      g_source_set_callback (skeleton->priv->changed_properties_idle_source, 
_gs_ephy_web_app_provider_emit_changed, g_object_ref (skeleton), (GDestroyNotify) g_object_unref);
+      g_source_set_name (skeleton->priv->changed_properties_idle_source, "[generated] 
_gs_ephy_web_app_provider_emit_changed");
+      g_source_attach (skeleton->priv->changed_properties_idle_source, skeleton->priv->context);
+      g_source_unref (skeleton->priv->changed_properties_idle_source);
+    }
+  g_mutex_unlock (&skeleton->priv->lock);
+}
+
+static void
+gs_ephy_web_app_provider_skeleton_set_property (GObject      *object,
+  guint         prop_id,
+  const GValue *value,
+  GParamSpec   *pspec)
+{
+  const _ExtendedGDBusPropertyInfo *info;
+  GsEphyWebAppProviderSkeleton *skeleton = GS_EPHY_WEB_APP_PROVIDER_SKELETON (object);
+  g_assert (prop_id != 0 && prop_id - 1 < 1);
+  info = (const _ExtendedGDBusPropertyInfo *) _gs_ephy_web_app_provider_property_info_pointers[prop_id - 1];
+  g_mutex_lock (&skeleton->priv->lock);
+  g_object_freeze_notify (object);
+  if (!_g_value_equal (value, &skeleton->priv->properties[prop_id - 1]))
+    {
+      if (g_dbus_interface_skeleton_get_connection (G_DBUS_INTERFACE_SKELETON (skeleton)) != NULL &&
+          info->emits_changed_signal)
+        _gs_ephy_web_app_provider_schedule_emit_changed (skeleton, info, prop_id, 
&skeleton->priv->properties[prop_id - 1]);
+      g_value_copy (value, &skeleton->priv->properties[prop_id - 1]);
+      g_object_notify_by_pspec (object, pspec);
+    }
+  g_mutex_unlock (&skeleton->priv->lock);
+  g_object_thaw_notify (object);
+}
+
+static void
+gs_ephy_web_app_provider_skeleton_init (GsEphyWebAppProviderSkeleton *skeleton)
+{
+#if GLIB_VERSION_MAX_ALLOWED >= GLIB_VERSION_2_38
+  skeleton->priv = gs_ephy_web_app_provider_skeleton_get_instance_private (skeleton);
+#else
+  skeleton->priv = G_TYPE_INSTANCE_GET_PRIVATE (skeleton, GS_EPHY_TYPE_WEB_APP_PROVIDER_SKELETON, 
GsEphyWebAppProviderSkeletonPrivate);
+#endif
+
+  g_mutex_init (&skeleton->priv->lock);
+  skeleton->priv->context = g_main_context_ref_thread_default ();
+  skeleton->priv->properties = g_new0 (GValue, 1);
+  g_value_init (&skeleton->priv->properties[0], G_TYPE_UINT);
+}
+
+static guint 
+gs_ephy_web_app_provider_skeleton_get_version (GsEphyWebAppProvider *object)
+{
+  GsEphyWebAppProviderSkeleton *skeleton = GS_EPHY_WEB_APP_PROVIDER_SKELETON (object);
+  guint value;
+  g_mutex_lock (&skeleton->priv->lock);
+  value = g_value_get_uint (&(skeleton->priv->properties[0]));
+  g_mutex_unlock (&skeleton->priv->lock);
+  return value;
+}
+
+static void
+gs_ephy_web_app_provider_skeleton_class_init (GsEphyWebAppProviderSkeletonClass *klass)
+{
+  GObjectClass *gobject_class;
+  GDBusInterfaceSkeletonClass *skeleton_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize = gs_ephy_web_app_provider_skeleton_finalize;
+  gobject_class->get_property = gs_ephy_web_app_provider_skeleton_get_property;
+  gobject_class->set_property = gs_ephy_web_app_provider_skeleton_set_property;
+  gobject_class->notify       = gs_ephy_web_app_provider_skeleton_notify;
+
+
+  gs_ephy_web_app_provider_override_properties (gobject_class, 1);
+
+  skeleton_class = G_DBUS_INTERFACE_SKELETON_CLASS (klass);
+  skeleton_class->get_info = gs_ephy_web_app_provider_skeleton_dbus_interface_get_info;
+  skeleton_class->get_properties = gs_ephy_web_app_provider_skeleton_dbus_interface_get_properties;
+  skeleton_class->flush = gs_ephy_web_app_provider_skeleton_dbus_interface_flush;
+  skeleton_class->get_vtable = gs_ephy_web_app_provider_skeleton_dbus_interface_get_vtable;
+
+#if GLIB_VERSION_MAX_ALLOWED < GLIB_VERSION_2_38
+  g_type_class_add_private (klass, sizeof (GsEphyWebAppProviderSkeletonPrivate));
+#endif
+}
+
+static void
+gs_ephy_web_app_provider_skeleton_iface_init (GsEphyWebAppProviderIface *iface)
+{
+  iface->get_version = gs_ephy_web_app_provider_skeleton_get_version;
+}
+
+/**
+ * gs_ephy_web_app_provider_skeleton_new:
+ *
+ * Creates a skeleton object for the D-Bus interface <link 
linkend="gdbus-interface-org-gnome-Epiphany-WebAppProvider.top_of_page">org.gnome.Epiphany.WebAppProvider</link>.
+ *
+ * Returns: (transfer full) (type GsEphyWebAppProviderSkeleton): The skeleton object.
+ */
+GsEphyWebAppProvider *
+gs_ephy_web_app_provider_skeleton_new (void)
+{
+  return GS_EPHY_WEB_APP_PROVIDER (g_object_new (GS_EPHY_TYPE_WEB_APP_PROVIDER_SKELETON, NULL));
+}
+
diff --git a/plugins/epiphany/gs-epiphany-generated.h b/plugins/epiphany/gs-epiphany-generated.h
new file mode 100644
index 000000000..464e47be6
--- /dev/null
+++ b/plugins/epiphany/gs-epiphany-generated.h
@@ -0,0 +1,248 @@
+/*
+ * This file is generated by gdbus-codegen, do not modify it.
+ *
+ * The license of this code is the same as for the D-Bus interface description
+ * it was derived from. Note that it links to GLib, so must comply with the
+ * LGPL linking clauses.
+ */
+
+#ifndef __GS_EPIPHANY_GENERATED_H__
+#define __GS_EPIPHANY_GENERATED_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+
+/* ------------------------------------------------------------------------ */
+/* Declarations for org.gnome.Epiphany.WebAppProvider */
+
+#define GS_EPHY_TYPE_WEB_APP_PROVIDER (gs_ephy_web_app_provider_get_type ())
+#define GS_EPHY_WEB_APP_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GS_EPHY_TYPE_WEB_APP_PROVIDER, 
GsEphyWebAppProvider))
+#define GS_EPHY_IS_WEB_APP_PROVIDER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GS_EPHY_TYPE_WEB_APP_PROVIDER))
+#define GS_EPHY_WEB_APP_PROVIDER_GET_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), 
GS_EPHY_TYPE_WEB_APP_PROVIDER, GsEphyWebAppProviderIface))
+
+struct _GsEphyWebAppProvider;
+typedef struct _GsEphyWebAppProvider GsEphyWebAppProvider;
+typedef struct _GsEphyWebAppProviderIface GsEphyWebAppProviderIface;
+
+struct _GsEphyWebAppProviderIface
+{
+  GTypeInterface parent_iface;
+
+
+  gboolean (*handle_get_installed_apps) (
+    GsEphyWebAppProvider *object,
+    GDBusMethodInvocation *invocation);
+
+  gboolean (*handle_install) (
+    GsEphyWebAppProvider *object,
+    GDBusMethodInvocation *invocation,
+    const gchar *arg_url,
+    const gchar *arg_name,
+    const gchar *arg_install_token);
+
+  gboolean (*handle_uninstall) (
+    GsEphyWebAppProvider *object,
+    GDBusMethodInvocation *invocation,
+    const gchar *arg_desktop_path);
+
+  guint  (*get_version) (GsEphyWebAppProvider *object);
+
+};
+
+GType gs_ephy_web_app_provider_get_type (void) G_GNUC_CONST;
+
+GDBusInterfaceInfo *gs_ephy_web_app_provider_interface_info (void);
+guint gs_ephy_web_app_provider_override_properties (GObjectClass *klass, guint property_id_begin);
+
+
+/* D-Bus method call completion functions: */
+void gs_ephy_web_app_provider_complete_get_installed_apps (
+    GsEphyWebAppProvider *object,
+    GDBusMethodInvocation *invocation,
+    const gchar *const *webapps);
+
+void gs_ephy_web_app_provider_complete_install (
+    GsEphyWebAppProvider *object,
+    GDBusMethodInvocation *invocation);
+
+void gs_ephy_web_app_provider_complete_uninstall (
+    GsEphyWebAppProvider *object,
+    GDBusMethodInvocation *invocation);
+
+
+
+/* D-Bus method calls: */
+void gs_ephy_web_app_provider_call_get_installed_apps (
+    GsEphyWebAppProvider *proxy,
+    GCancellable *cancellable,
+    GAsyncReadyCallback callback,
+    gpointer user_data);
+
+gboolean gs_ephy_web_app_provider_call_get_installed_apps_finish (
+    GsEphyWebAppProvider *proxy,
+    gchar ***out_webapps,
+    GAsyncResult *res,
+    GError **error);
+
+gboolean gs_ephy_web_app_provider_call_get_installed_apps_sync (
+    GsEphyWebAppProvider *proxy,
+    gchar ***out_webapps,
+    GCancellable *cancellable,
+    GError **error);
+
+void gs_ephy_web_app_provider_call_install (
+    GsEphyWebAppProvider *proxy,
+    const gchar *arg_url,
+    const gchar *arg_name,
+    const gchar *arg_install_token,
+    GCancellable *cancellable,
+    GAsyncReadyCallback callback,
+    gpointer user_data);
+
+gboolean gs_ephy_web_app_provider_call_install_finish (
+    GsEphyWebAppProvider *proxy,
+    GAsyncResult *res,
+    GError **error);
+
+gboolean gs_ephy_web_app_provider_call_install_sync (
+    GsEphyWebAppProvider *proxy,
+    const gchar *arg_url,
+    const gchar *arg_name,
+    const gchar *arg_install_token,
+    GCancellable *cancellable,
+    GError **error);
+
+void gs_ephy_web_app_provider_call_uninstall (
+    GsEphyWebAppProvider *proxy,
+    const gchar *arg_desktop_path,
+    GCancellable *cancellable,
+    GAsyncReadyCallback callback,
+    gpointer user_data);
+
+gboolean gs_ephy_web_app_provider_call_uninstall_finish (
+    GsEphyWebAppProvider *proxy,
+    GAsyncResult *res,
+    GError **error);
+
+gboolean gs_ephy_web_app_provider_call_uninstall_sync (
+    GsEphyWebAppProvider *proxy,
+    const gchar *arg_desktop_path,
+    GCancellable *cancellable,
+    GError **error);
+
+
+
+/* D-Bus property accessors: */
+guint gs_ephy_web_app_provider_get_version (GsEphyWebAppProvider *object);
+void gs_ephy_web_app_provider_set_version (GsEphyWebAppProvider *object, guint value);
+
+
+/* ---- */
+
+#define GS_EPHY_TYPE_WEB_APP_PROVIDER_PROXY (gs_ephy_web_app_provider_proxy_get_type ())
+#define GS_EPHY_WEB_APP_PROVIDER_PROXY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), 
GS_EPHY_TYPE_WEB_APP_PROVIDER_PROXY, GsEphyWebAppProviderProxy))
+#define GS_EPHY_WEB_APP_PROVIDER_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), 
GS_EPHY_TYPE_WEB_APP_PROVIDER_PROXY, GsEphyWebAppProviderProxyClass))
+#define GS_EPHY_WEB_APP_PROVIDER_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
GS_EPHY_TYPE_WEB_APP_PROVIDER_PROXY, GsEphyWebAppProviderProxyClass))
+#define GS_EPHY_IS_WEB_APP_PROVIDER_PROXY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
GS_EPHY_TYPE_WEB_APP_PROVIDER_PROXY))
+#define GS_EPHY_IS_WEB_APP_PROVIDER_PROXY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), 
GS_EPHY_TYPE_WEB_APP_PROVIDER_PROXY))
+
+typedef struct _GsEphyWebAppProviderProxy GsEphyWebAppProviderProxy;
+typedef struct _GsEphyWebAppProviderProxyClass GsEphyWebAppProviderProxyClass;
+typedef struct _GsEphyWebAppProviderProxyPrivate GsEphyWebAppProviderProxyPrivate;
+
+struct _GsEphyWebAppProviderProxy
+{
+  /*< private >*/
+  GDBusProxy parent_instance;
+  GsEphyWebAppProviderProxyPrivate *priv;
+};
+
+struct _GsEphyWebAppProviderProxyClass
+{
+  GDBusProxyClass parent_class;
+};
+
+GType gs_ephy_web_app_provider_proxy_get_type (void) G_GNUC_CONST;
+
+#if GLIB_CHECK_VERSION(2, 44, 0)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GsEphyWebAppProviderProxy, g_object_unref)
+#endif
+
+void gs_ephy_web_app_provider_proxy_new (
+    GDBusConnection     *connection,
+    GDBusProxyFlags      flags,
+    const gchar         *name,
+    const gchar         *object_path,
+    GCancellable        *cancellable,
+    GAsyncReadyCallback  callback,
+    gpointer             user_data);
+GsEphyWebAppProvider *gs_ephy_web_app_provider_proxy_new_finish (
+    GAsyncResult        *res,
+    GError             **error);
+GsEphyWebAppProvider *gs_ephy_web_app_provider_proxy_new_sync (
+    GDBusConnection     *connection,
+    GDBusProxyFlags      flags,
+    const gchar         *name,
+    const gchar         *object_path,
+    GCancellable        *cancellable,
+    GError             **error);
+
+void gs_ephy_web_app_provider_proxy_new_for_bus (
+    GBusType             bus_type,
+    GDBusProxyFlags      flags,
+    const gchar         *name,
+    const gchar         *object_path,
+    GCancellable        *cancellable,
+    GAsyncReadyCallback  callback,
+    gpointer             user_data);
+GsEphyWebAppProvider *gs_ephy_web_app_provider_proxy_new_for_bus_finish (
+    GAsyncResult        *res,
+    GError             **error);
+GsEphyWebAppProvider *gs_ephy_web_app_provider_proxy_new_for_bus_sync (
+    GBusType             bus_type,
+    GDBusProxyFlags      flags,
+    const gchar         *name,
+    const gchar         *object_path,
+    GCancellable        *cancellable,
+    GError             **error);
+
+
+/* ---- */
+
+#define GS_EPHY_TYPE_WEB_APP_PROVIDER_SKELETON (gs_ephy_web_app_provider_skeleton_get_type ())
+#define GS_EPHY_WEB_APP_PROVIDER_SKELETON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), 
GS_EPHY_TYPE_WEB_APP_PROVIDER_SKELETON, GsEphyWebAppProviderSkeleton))
+#define GS_EPHY_WEB_APP_PROVIDER_SKELETON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), 
GS_EPHY_TYPE_WEB_APP_PROVIDER_SKELETON, GsEphyWebAppProviderSkeletonClass))
+#define GS_EPHY_WEB_APP_PROVIDER_SKELETON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
GS_EPHY_TYPE_WEB_APP_PROVIDER_SKELETON, GsEphyWebAppProviderSkeletonClass))
+#define GS_EPHY_IS_WEB_APP_PROVIDER_SKELETON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
GS_EPHY_TYPE_WEB_APP_PROVIDER_SKELETON))
+#define GS_EPHY_IS_WEB_APP_PROVIDER_SKELETON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), 
GS_EPHY_TYPE_WEB_APP_PROVIDER_SKELETON))
+
+typedef struct _GsEphyWebAppProviderSkeleton GsEphyWebAppProviderSkeleton;
+typedef struct _GsEphyWebAppProviderSkeletonClass GsEphyWebAppProviderSkeletonClass;
+typedef struct _GsEphyWebAppProviderSkeletonPrivate GsEphyWebAppProviderSkeletonPrivate;
+
+struct _GsEphyWebAppProviderSkeleton
+{
+  /*< private >*/
+  GDBusInterfaceSkeleton parent_instance;
+  GsEphyWebAppProviderSkeletonPrivate *priv;
+};
+
+struct _GsEphyWebAppProviderSkeletonClass
+{
+  GDBusInterfaceSkeletonClass parent_class;
+};
+
+GType gs_ephy_web_app_provider_skeleton_get_type (void) G_GNUC_CONST;
+
+#if GLIB_CHECK_VERSION(2, 44, 0)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GsEphyWebAppProviderSkeleton, g_object_unref)
+#endif
+
+GsEphyWebAppProvider *gs_ephy_web_app_provider_skeleton_new (void);
+
+
+G_END_DECLS
+
+#endif /* __GS_EPIPHANY_GENERATED_H__ */
diff --git a/plugins/epiphany/gs-plugin-epiphany.c b/plugins/epiphany/gs-plugin-epiphany.c
index 9a83e8004..131f7e245 100644
--- a/plugins/epiphany/gs-plugin-epiphany.c
+++ b/plugins/epiphany/gs-plugin-epiphany.c
@@ -1,322 +1,569 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
  *
- * Copyright (C) 2013-2014 Richard Hughes <richard hughsie com>
- * Copyright (C) 2015 Kalev Lember <klember redhat com>
+ * Copyright (C) 2021 Matthew Leeds <mwleeds protonmail com>
  *
  * SPDX-License-Identifier: GPL-2.0+
  */
 
 #include <config.h>
-
-#include <string.h>
-
+#include <glib/gi18n.h>
 #include <gnome-software.h>
+#include <fcntl.h>
+#include <gio/gunixfdlist.h>
+#include <glib/gstdio.h>
+
+#include "gs-epiphany-generated.h"
+#include "gs-plugin-epiphany.h"
 
 /*
  * SECTION:
- * Uses epiphany to launch web applications.
+ * This plugin uses Epiphany to install, launch, and uninstall web applications.
  *
- * If the epiphany binary is not present then it self-disables.
+ * If the org.gnome.Epiphany.WebAppProvider D-Bus interface is not present or
+ * the DynamicLauncher portal is not available then it self-disables. This
+ * should work with both Flatpak'd and not Flatpak'd Epiphany, for new enough
+ * versions of Epiphany.
  */
 
-void
-gs_plugin_initialize (GsPlugin *plugin)
+struct _GsPluginEpiphany
+{
+       GsPlugin parent;
+
+       GsWorkerThread *worker;  /* (owned) */
+
+       GsEphyWebAppProvider *epiphany_proxy;  /* (owned) */
+       GDBusProxy *launcher_portal_proxy;  /* (owned) */
+};
+
+G_DEFINE_TYPE (GsPluginEpiphany, gs_plugin_epiphany, GS_TYPE_PLUGIN)
+
+#define assert_in_worker(self) \
+       g_assert (gs_worker_thread_is_in_worker_context (self->worker))
+
+static void
+gs_epiphany_error_convert (GError **perror)
+{
+       GError *error = perror != NULL ? *perror : NULL;
+
+       /* not set */
+       if (error == NULL)
+               return;
+
+       /* parse remote epiphany-webapp-provider error */
+       if (g_dbus_error_is_remote_error (error)) {
+               g_autofree gchar *remote_error = g_dbus_error_get_remote_error (error);
+
+               g_dbus_error_strip_remote_error (error);
+
+               if (g_str_equal (remote_error, "org.freedesktop.DBus.Error.ServiceUnknown")) {
+                       error->code = GS_PLUGIN_ERROR_NOT_SUPPORTED;
+               } else {
+                       g_warning ("Can’t reliably fixup remote error ‘%s’", remote_error);
+                       error->code = GS_PLUGIN_ERROR_FAILED;
+               }
+               error->domain = GS_PLUGIN_ERROR;
+               return;
+       }
+
+       /* this is allowed for low-level errors */
+       if (gs_utils_error_convert_gio (perror))
+               return;
+
+       /* this is allowed for low-level errors */
+       if (gs_utils_error_convert_gdbus (perror))
+               return;
+}
+
+static void setup_thread_cb (GTask        *task,
+                             gpointer      source_object,
+                             gpointer      task_data,
+                             GCancellable *cancellable);
+
+static void
+gs_plugin_epiphany_setup_async (GsPlugin            *plugin,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
+       g_autoptr(GTask) task = NULL;
+
+       task = g_task_new (plugin, cancellable, callback, user_data);
+       g_task_set_source_tag (task, gs_plugin_epiphany_setup_async);
+
+       g_debug ("%s", G_STRFUNC);
+
+       /* Start up a worker thread to process all the plugin’s function calls. */
+       self->worker = gs_worker_thread_new ("gs-plugin-epiphany");
+
+       /* Queue a job to find installed apps */
+       gs_worker_thread_queue (self->worker, G_PRIORITY_DEFAULT,
+                               setup_thread_cb, g_steal_pointer (&task));
+}
+
+/* Run in @worker */
+static void
+setup_thread_cb (GTask        *task,
+                gpointer      source_object,
+                gpointer      task_data,
+                GCancellable *cancellable)
+{
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (source_object);
+       g_autofree gchar *name_owner = NULL;
+       g_autoptr(GError) local_error = NULL;
+       g_autoptr(GDBusProxy) portal_proxy = NULL;
+       g_autoptr(GVariant) version = NULL;
+       g_autoptr(GVariant) version_child = NULL;
+       g_autoptr(GVariant) version_grandchild = NULL;
+
+       assert_in_worker (self);
+
+       /* Check that the proxy exists (and is owned; it should auto-start) so
+        * we can disable the plugin for systems which don’t have new enough
+        * Epiphany.
+        */
+       self->epiphany_proxy = gs_ephy_web_app_provider_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+                                                                               G_DBUS_PROXY_FLAGS_NONE,
+                                                                               
"org.gnome.Epiphany.WebAppProvider",
+                                                                               
"/org/gnome/Epiphany/WebAppProvider",
+                                                                               g_task_get_cancellable (task),
+                                                                               &local_error);
+       if (self->epiphany_proxy == NULL) {
+               gs_epiphany_error_convert (&local_error);
+               g_task_return_error (task, g_steal_pointer (&local_error));
+               return;
+       }
+
+       name_owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (self->epiphany_proxy));
+
+       if (name_owner == NULL) {
+               g_task_return_new_error (task, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                        "Couldn’t create Epiphany WebAppProvider proxy: couldn’t get name 
owner");
+               return;
+       }
+
+       /* Check if the dynamic launcher portal is available and disable otherwise */
+       portal_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",
+                                                     g_task_get_cancellable (task),
+                                                     &local_error);
+       if (portal_proxy == NULL) {
+               gs_epiphany_error_convert (&local_error);
+               g_task_return_error (task, g_steal_pointer (&local_error));
+               return;
+       }
+       version = g_dbus_proxy_call_sync (portal_proxy, "Get",
+                                         g_variant_new ("(ss)", "org.freedesktop.portal.DynamicLauncher", 
"version"),
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1, NULL, NULL);
+       if (version == NULL) {
+               g_task_return_new_error (task, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                                        "Dynamic launcher portal not available");
+               return;
+       } else {
+               version_child = g_variant_get_child_value (version, 0);
+               version_grandchild = g_variant_get_child_value (version_child, 0);
+               g_debug ("Found version %" G_GUINT32_FORMAT " of the dynamic launcher portal", 
g_variant_get_uint32 (version_grandchild));
+       }
+
+       /* And finally, make a proxy object for the dynamic launcher portal */
+       self->launcher_portal_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.portal.DynamicLauncher",
+                                                                    g_task_get_cancellable (task),
+                                                                    &local_error);
+       if (self->launcher_portal_proxy == NULL) {
+               gs_epiphany_error_convert (&local_error);
+               g_task_return_error (task, g_steal_pointer (&local_error));
+               return;
+       }
+
+       g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+gs_plugin_epiphany_setup_finish (GsPlugin      *plugin,
+                                 GAsyncResult  *result,
+                                 GError       **error)
+{
+       return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void shutdown_cb (GObject      *source_object,
+                         GAsyncResult *result,
+                         gpointer      user_data);
+
+static void
+gs_plugin_epiphany_shutdown_async (GsPlugin            *plugin,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
 {
-       g_autofree gchar *epiphany = NULL;
-
-       /* we can only work with epiphany */
-       epiphany = g_find_program_in_path ("epiphany");
-       if (epiphany == NULL) {
-               gs_plugin_set_enabled (plugin, FALSE);
-               g_debug ("disabling '%s' as epiphany does not exist",
-                        gs_plugin_get_name (plugin));
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
+       g_autoptr(GTask) task = NULL;
+
+       task = g_task_new (self, cancellable, callback, user_data);
+       g_task_set_source_tag (task, gs_plugin_epiphany_shutdown_async);
+
+       /* Stop the worker thread. */
+       gs_worker_thread_shutdown_async (self->worker, cancellable, shutdown_cb, g_steal_pointer (&task));
+}
+
+static void
+shutdown_cb (GObject      *source_object,
+             GAsyncResult *result,
+             gpointer      user_data)
+{
+       g_autoptr(GTask) task = G_TASK (user_data);
+       GsPluginEpiphany *self = g_task_get_source_object (task);
+       g_autoptr(GsWorkerThread) worker = NULL;
+       g_autoptr(GError) local_error = NULL;
+
+       worker = g_steal_pointer (&self->worker);
+
+       if (!gs_worker_thread_shutdown_finish (worker, result, &local_error)) {
+               g_task_return_error (task, g_steal_pointer (&local_error));
+               return;
        }
 
+       g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+gs_plugin_epiphany_shutdown_finish (GsPlugin      *plugin,
+                                    GAsyncResult  *result,
+                                    GError       **error)
+{
+       return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+gs_plugin_epiphany_init (GsPluginEpiphany *self)
+{
        /* set name of MetaInfo file */
-       gs_plugin_set_appstream_id (plugin, "org.gnome.Software.Plugin.Epiphany");
+       gs_plugin_set_appstream_id (GS_PLUGIN (self), "org.gnome.Software.Plugin.Epiphany");
 
        /* need help from appstream */
-       gs_plugin_add_rule (plugin, GS_PLUGIN_RULE_RUN_AFTER, "appstream");
+       gs_plugin_add_rule (GS_PLUGIN (self), GS_PLUGIN_RULE_RUN_AFTER, "appstream");
+}
+
+static void
+gs_plugin_epiphany_dispose (GObject *object)
+{
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (object);
+
+       g_clear_object (&self->epiphany_proxy);
+       g_clear_object (&self->launcher_portal_proxy);
+       g_clear_object (&self->worker);
+
+       G_OBJECT_CLASS (gs_plugin_epiphany_parent_class)->dispose (object);
 }
 
 void
-gs_plugin_adopt_app (GsPlugin *plugin, GsApp *app)
+gs_plugin_adopt_app (GsPlugin *plugin,
+                    GsApp    *app)
 {
-       if (gs_app_get_kind (app) == AS_APP_KIND_WEB_APP &&
+       if (gs_app_get_kind (app) == AS_COMPONENT_KIND_WEB_APP &&
            gs_app_get_bundle_kind (app) != AS_BUNDLE_KIND_PACKAGE) {
-               gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
+               gs_app_set_management_plugin (app, plugin);
        }
+
+       if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN)
+               gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
 }
 
-static gchar *
-_gs_app_get_id_nonfull (GsApp *app)
+static void list_installed_apps_thread_cb (GTask        *task,
+                                           gpointer      source_object,
+                                           gpointer      task_data,
+                                           GCancellable *cancellable);
+
+static void
+gs_plugin_epiphany_list_installed_apps_async (GsPlugin            *plugin,
+                                              GCancellable        *cancellable,
+                                              GAsyncReadyCallback  callback,
+                                              gpointer             user_data)
 {
-       gchar *id;
-       gchar *tmp;
-
-       id = g_strdup (gs_app_get_id (app));
-       tmp = g_strrstr (id, ".desktop");
-       if (tmp != NULL)
-               *tmp = '\0';
-       return id;
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
+       g_autoptr(GTask) task = NULL;
+
+       task = g_task_new (plugin, cancellable, callback, user_data);
+       g_task_set_source_tag (task, gs_plugin_epiphany_list_installed_apps_async);
+
+       /* Queue a job to get the installed apps. */
+       gs_worker_thread_queue (self->worker, G_PRIORITY_DEFAULT,
+                               list_installed_apps_thread_cb, g_steal_pointer (&task));
+}
+
+/* Run in @worker */
+static void
+list_installed_apps_thread_cb (GTask        *task,
+                               gpointer      source_object,
+                               gpointer      task_data,
+                               GCancellable *cancellable)
+{
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (source_object);
+       g_autoptr(GsAppList) list = gs_app_list_new ();
+       g_autoptr(GError) local_error = NULL;
+       g_autoptr(GVariant) webapps_v = NULL;
+       g_auto(GStrv) webapps = NULL;
+       guint n_webapps;
+
+       assert_in_worker (self);
+
+       if (!gs_ephy_web_app_provider_call_get_installed_apps_sync (self->epiphany_proxy,
+                                                                   &webapps,
+                                                                   cancellable,
+                                                                   &local_error)) {
+               gs_epiphany_error_convert (&local_error);
+               g_task_return_error (task, g_steal_pointer (&local_error));
+               return;
+       }
+
+       n_webapps = g_strv_length (webapps);
+       g_debug ("%s: epiphany-webapp-provider returned %u installed web apps", G_STRFUNC, n_webapps);
+       for (guint i = 0; i < n_webapps; i++) {
+               const gchar *desktop_file_id = webapps[i];
+               const gchar *desktop_path;
+               const gchar *name;
+               const gchar *url;
+               g_autofree char *icon_path = NULL;
+               const gchar *exec;
+               int argc;
+               g_auto (GStrv) argv = NULL;
+               guint64 install_date = 0;
+               g_autoptr(GsApp) app = NULL;
+               g_autoptr(GDesktopAppInfo) desktop_info = NULL;
+               g_autoptr(GFileInfo) file_info = NULL;
+               g_autoptr(GFile) desktop_file = NULL;
+
+               g_debug ("%s: Working on installed web app %s", G_STRFUNC, desktop_file_id);
+
+               desktop_info = g_desktop_app_info_new (desktop_file_id);
+               if (desktop_info == NULL) {
+                       g_warning ("Epiphany returned a non-existent desktop ID %s", desktop_file_id);
+                       continue;
+               }
+
+               name = g_app_info_get_name (G_APP_INFO (desktop_info));
+
+               /* This way of getting the URL is a bit hacky but it's what Epiphany does */
+               exec = g_app_info_get_commandline (G_APP_INFO (desktop_info));
+               if (g_shell_parse_argv (exec, &argc, &argv, NULL)) {
+                       url = argv[argc - 1];
+               } else {
+                       g_warning ("Failed to parse URL for web app %s", desktop_file_id);
+                       continue;
+               }
+
+               icon_path = g_desktop_app_info_get_string (desktop_info, "Icon");
+
+               desktop_path = g_desktop_app_info_get_filename (desktop_info);
+               g_assert (desktop_path);
+               desktop_file = g_file_new_for_path (desktop_path);
+
+               /* FIXME: this should use TIME_CREATED but it does not seem to
+                * be working (copied from Epiphany) */
+               file_info = g_file_query_info (desktop_file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL);
+               install_date = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+               //TODO do we need to check a cache here, see if a GsApp already exists?
+               app = gs_app_new (desktop_file_id);
+               gs_app_set_management_plugin (app, GS_PLUGIN (self));
+               gs_app_set_state (app, GS_APP_STATE_INSTALLED);
+               gs_app_set_kind (app, AS_COMPONENT_KIND_WEB_APP);
+               gs_app_set_name (app, GS_APP_QUALITY_NORMAL, name);
+               gs_app_set_url (app, AS_URL_KIND_HOMEPAGE, url);
+
+               if (icon_path) {
+                       g_autoptr(GFile) icon_file = g_file_new_for_path (icon_path);
+                       g_autoptr(GIcon) icon = g_file_icon_new (icon_file);
+                       gs_utils_file_icon_ensure_size (icon);
+                       gs_app_add_icon (app, icon);
+               }
+               if (install_date) {
+                       gs_app_set_install_date (app, install_date);
+               }
+               gs_app_list_add (list, app);
+       }
+
+
+       g_task_return_pointer (task, g_steal_pointer (&list), g_object_unref);
+}
+
+static GsAppList *
+gs_plugin_epiphany_list_installed_apps_finish (GsPlugin      *plugin,
+                                               GAsyncResult  *result,
+                                               GError       **error)
+{
+       return g_task_propagate_pointer (G_TASK (result), error);
 }
 
 gboolean
-gs_plugin_app_install (GsPlugin *plugin, GsApp *app,
-                      GCancellable *cancellable, GError **error)
+gs_plugin_app_install (GsPlugin      *plugin,
+                      GsApp         *app,
+                      GCancellable  *cancellable,
+                      GError       **error)
 {
-       AsIcon *icon;
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
+       const char *url;
+       const char *name;
+       const char *icon_format;
        GPtrArray *icons;
-       gboolean ret = TRUE;
-       gsize kf_length;
-       g_autoptr(GError) error_local = NULL;
-       g_autofree gchar *app_desktop = NULL;
-       g_autofree gchar *epi_desktop = NULL;
-       g_autofree gchar *epi_dir = NULL;
-       g_autofree gchar *epi_icon = NULL;
-       g_autofree gchar *exec = NULL;
-       g_autofree gchar *hash = NULL;
-       g_autofree gchar *id_nonfull = NULL;
-       g_autofree gchar *kf_data = NULL;
-       g_autofree gchar *wmclass = NULL;
-       g_autoptr(GKeyFile) kf = NULL;
-       g_autoptr(GFile) symlink_desktop = NULL;
-       g_autoptr(GFile) symlink_icon = NULL;
-       const gchar *url = NULL;
-
-       /* only process this app if was created by this plugin */
-       if (g_strcmp0 (gs_app_get_management_plugin (app),
-                      gs_plugin_get_name (plugin)) != 0)
+       g_autoptr(GVariant) token_v = NULL;
+       g_autoptr(GVariant) icon_v = NULL;
+       GVariantBuilder opt_builder;
+
+       if (!gs_app_has_management_plugin (app, plugin))
                return TRUE;
 
-       /* create the correct directory */
-       id_nonfull = _gs_app_get_id_nonfull (app);
-       hash = g_compute_checksum_for_string (G_CHECKSUM_SHA1, gs_app_get_name (app), -1);
-       epi_dir = g_strdup_printf ("%s/epiphany/app-%s-%s",
-                                  g_get_user_config_dir (),
-                                  id_nonfull,
-                                  hash);
-       g_mkdir_with_parents (epi_dir, 0755);
-
-       /* symlink icon */
-       epi_icon = g_build_filename (epi_dir, "app-icon.png", NULL);
-       symlink_icon = g_file_new_for_path (epi_icon);
-       icons = gs_app_get_icons (app);
-       if (icons->len == 0) {
-               g_set_error (error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                            "no icons for %s",
+       url = gs_app_get_url (app, AS_URL_KIND_HOMEPAGE);
+       if (url == NULL || *url == '\0') {
+               g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED,
+                            "Can't install web app %s without url",
                             gs_app_get_id (app));
                return FALSE;
        }
-       icon = g_ptr_array_index (icons, 0);
-       if (as_icon_get_filename (icon) == NULL) {
-               g_set_error (error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
-                            "no filename for icon %s",
-                            as_icon_get_name (icon));
+       name = gs_app_get_name (app);
+       if (name == NULL || *name == '\0') {
+               g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED,
+                            "Can't install web app %s without name",
+                            gs_app_get_id (app));
                return FALSE;
        }
-       ret = g_file_make_symbolic_link (symlink_icon,
-                                        as_icon_get_filename (icon),
-                                        NULL,
-                                        &error_local);
-       if (!ret) {
-               if (g_error_matches (error_local, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
-                       g_debug ("ignoring icon symlink failure: %s",
-                                error_local->message);
-               } else {
-                       g_set_error (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_WRITE_FAILED,
-                                    "Can't symlink icon: %s",
-                                    error_local->message);
-                       return FALSE;
+       icons = gs_app_get_icons (app);
+       for (guint i = 0; icons != NULL && i < icons->len; i++) {
+               GIcon *icon = g_ptr_array_index (icons, i);
+               /* Note: GsRemoteIcon will work on this GFileIcon code path.
+                * The icons plugin should have called
+                * gs_app_ensure_icons_downloaded() for us
+                */
+               if (G_IS_FILE_ICON (icon)) {
+                       g_autofree char *icon_path = NULL;
+                       g_autoptr(GInputStream) stream = NULL;
+                       g_autoptr(GBytes) bytes = NULL;
+                       g_autoptr(GIcon) bytes_icon = NULL;
+
+                       icon_path = g_file_get_path (g_file_icon_get_file (G_FILE_ICON (icon)));
+                       if (g_str_has_suffix (icon_path, ".png"))
+                               icon_format = "png";
+                       else if (g_str_has_suffix (icon_path, ".svg"))
+                               icon_format = "svg";
+                       else if (g_str_has_suffix (icon_path, ".jpeg") || g_str_has_suffix (icon_path, 
".jpg"))
+                               icon_format = "jpeg";
+                       else
+                               continue;
+
+                       stream = g_loadable_icon_load (G_LOADABLE_ICON (icon), 0, NULL, NULL, NULL);
+                       if (stream)
+                               bytes = g_input_stream_read_bytes (stream, 10485760 /* 10 MiB */, NULL, NULL);
+                       if (bytes)
+                               bytes_icon = g_bytes_icon_new (bytes);
+                       if (bytes_icon)
+                               icon_v = g_icon_serialize (bytes_icon);
+                       if (icon_v)
+                               break;
                }
        }
-
-       /* add desktop file */
-       wmclass = g_strdup_printf ("%s-%s", id_nonfull, hash);
-       kf = g_key_file_new ();
-       g_key_file_set_string (kf,
-                              G_KEY_FILE_DESKTOP_GROUP,
-                              G_KEY_FILE_DESKTOP_KEY_NAME,
-                              gs_app_get_name (app));
-       g_key_file_set_string (kf,
-                              G_KEY_FILE_DESKTOP_GROUP,
-                              G_KEY_FILE_DESKTOP_KEY_COMMENT,
-                              gs_app_get_summary (app));
-       url = gs_app_get_launchable (app, AS_LAUNCHABLE_KIND_URL);
-       if (url == NULL)
-               url = gs_app_get_url (app, AS_URL_KIND_HOMEPAGE);
-       exec = g_strdup_printf ("epiphany --application-mode --profile=\"%s\" %s",
-                               epi_dir,
-                               url);
-       g_key_file_set_string (kf,
-                              G_KEY_FILE_DESKTOP_GROUP,
-                              G_KEY_FILE_DESKTOP_KEY_EXEC,
-                              exec);
-       g_key_file_set_boolean (kf,
-                               G_KEY_FILE_DESKTOP_GROUP,
-                               G_KEY_FILE_DESKTOP_KEY_STARTUP_NOTIFY,
-                               TRUE);
-       g_key_file_set_boolean (kf,
-                               G_KEY_FILE_DESKTOP_GROUP,
-                               G_KEY_FILE_DESKTOP_KEY_TERMINAL,
-                               FALSE);
-       g_key_file_set_boolean (kf,
-                               G_KEY_FILE_DESKTOP_GROUP,
-                               G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY,
-                               FALSE);
-       g_key_file_set_string (kf,
-                              G_KEY_FILE_DESKTOP_GROUP,
-                              G_KEY_FILE_DESKTOP_KEY_TYPE,
-                              G_KEY_FILE_DESKTOP_TYPE_APPLICATION);
-       g_key_file_set_string (kf,
-                              G_KEY_FILE_DESKTOP_GROUP,
-                              G_KEY_FILE_DESKTOP_KEY_ICON,
-                              epi_icon);
-       g_key_file_set_string (kf,
-                              G_KEY_FILE_DESKTOP_GROUP,
-                              G_KEY_FILE_DESKTOP_KEY_STARTUP_WM_CLASS,
-                              wmclass);
-
-       /* save keyfile */
-       kf_data = g_key_file_to_data (kf, &kf_length, error);
-       if (kf_data == NULL)
+       if (icon_v == NULL) {
+               g_set_error (error, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_FAILED,
+                            "Can't install web app %s without icon",
+                            gs_app_get_id (app));
                return FALSE;
-       epi_desktop = g_strdup_printf ("%s/%s.desktop", epi_dir, wmclass);
-       if (!g_file_set_contents (epi_desktop, kf_data, (gssize) kf_length, error))
+       }
+
+       gs_app_set_state (app, GS_APP_STATE_INSTALLING);
+       /* First get a token from xdg-desktop-portal so Epiphany can do the
+        * installation without user confirmation
+        */
+       g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
+       token_v = g_dbus_proxy_call_sync (self->launcher_portal_proxy,
+                                         "RequestInstallToken",
+                                         g_variant_new ("(svsa{sv})",
+                                                        name, icon_v, icon_format, &opt_builder),
+                                         G_DBUS_CALL_FLAGS_NONE,
+                                         -1, cancellable, error);
+       if (token_v == NULL) {
+               gs_epiphany_error_convert (error);
+               gs_app_set_state_recover (app);
                return FALSE;
+       }
 
-       /* symlink it to somewhere the shell will notice */
-       app_desktop = g_build_filename (g_get_user_data_dir (),
-                                       "applications",
-                                       gs_app_get_id (app),
-                                       NULL);
-       symlink_desktop = g_file_new_for_path (app_desktop);
-       ret = g_file_make_symbolic_link (symlink_desktop,
-                                        epi_desktop,
-                                        NULL,
-                                        error);
-       if (!ret) {
-               gs_utils_error_convert_gio (error);
+       /* Then pass the token to Epiphany which will use xdg-desktop-portal to
+        * complete the installation
+        */
+       if (!gs_ephy_web_app_provider_call_install_sync (self->epiphany_proxy,
+                                                        url, name,
+                                                        g_variant_get_string (token_v, NULL),
+                                                        cancellable,
+                                                        error)) {
+               gs_epiphany_error_convert (error);
+               gs_app_set_state_recover (app);
                return FALSE;
        }
+       gs_app_set_state (app, GS_APP_STATE_INSTALLED);
 
-       /* update state */
-       gs_app_set_state (app, AS_APP_STATE_INSTALLING);
-       gs_app_set_state (app, AS_APP_STATE_INSTALLED);
        return TRUE;
 }
 
 gboolean
-gs_plugin_app_remove (GsPlugin *plugin, GsApp *app,
-                     GCancellable *cancellable, GError **error)
+gs_plugin_app_remove (GsPlugin      *plugin,
+                     GsApp         *app,
+                     GCancellable  *cancellable,
+                     GError       **error)
 {
-       const gchar *epi_desktop;
-       g_autofree gchar *app_desktop = NULL;
-       g_autoptr(GFile) file_epi = NULL;
-       g_autoptr(GFile) file_app = NULL;
-
-       /* only process this app if was created by this plugin */
-       if (g_strcmp0 (gs_app_get_management_plugin (app),
-                      gs_plugin_get_name (plugin)) != 0)
-               return TRUE;
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
 
-       /* remove the epi 'config' file */
-       gs_app_set_state (app, AS_APP_STATE_REMOVING);
-       epi_desktop = gs_app_get_source_id_default (app);
-       file_epi = g_file_new_for_path (epi_desktop);
-       if (!g_file_delete (file_epi, NULL, error))
-               return FALSE;
+       if (!gs_app_has_management_plugin (app, plugin))
+               return TRUE;
 
-       /* remove the shared desktop file */
-       app_desktop = g_build_filename (g_get_user_data_dir (),
-                                       "applications",
-                                       gs_app_get_id (app),
-                                       NULL);
-       file_app = g_file_new_for_path (app_desktop);
-       if (!g_file_delete (file_app, NULL, error)) {
-               gs_utils_error_convert_gio (error);
+       gs_app_set_state (app, GS_APP_STATE_REMOVING);
+       if (!gs_ephy_web_app_provider_call_uninstall_sync (self->epiphany_proxy,
+                                                          gs_app_get_id (app),
+                                                          cancellable,
+                                                          error)) {
+               gs_epiphany_error_convert (error);
+               gs_app_set_state_recover (app);
                return FALSE;
        }
-       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
+       gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
+
        return TRUE;
 }
 
 gboolean
-gs_plugin_refine_app (GsPlugin *plugin,
-                     GsApp *app,
-                     GsPluginRefineFlags flags,
-                     GCancellable *cancellable,
-                     GError **error)
+gs_plugin_launch (GsPlugin      *plugin,
+                 GsApp         *app,
+                 GCancellable  *cancellable,
+                 GError       **error)
 {
-       const gchar *name;
-       g_autofree gchar *fn = NULL;
-       g_autofree gchar *hash = NULL;
-       g_autofree gchar *id_nonfull = NULL;
-
-       /* only process this app if was created by this plugin */
-       if (g_strcmp0 (gs_app_get_management_plugin (app),
-                      gs_plugin_get_name (plugin)) != 0)
+       if (!gs_app_has_management_plugin (app, plugin))
                return TRUE;
 
-       gs_app_set_size_installed (app, 4096);
+       return gs_plugin_app_launch (plugin, app, error);
+}
 
-       /* i guess this is technically true */
-       gs_app_add_kudo (app, GS_APP_KUDO_SANDBOXED_SECURE);
+static void
+gs_plugin_epiphany_class_init (GsPluginEpiphanyClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GsPluginClass *plugin_class = GS_PLUGIN_CLASS (klass);
 
-       name = gs_app_get_name (app);
-       if (name == NULL) {
-               g_set_error (error,
-                            GS_PLUGIN_ERROR,
-                            GS_PLUGIN_ERROR_INVALID_FORMAT,
-                            "name unset for %s",
-                            gs_app_get_id (app));
-               return FALSE;
-       }
-       if (gs_app_get_summary (app) == NULL) {
-               g_debug ("faking summary for %s", gs_app_get_id (app));
-               gs_app_set_summary (app, GS_APP_QUALITY_LOWEST,
-                                   "Web Application");
-       }
-       hash = g_compute_checksum_for_string (G_CHECKSUM_SHA1, name, -1);
-       id_nonfull = _gs_app_get_id_nonfull (app);
-       fn = g_strdup_printf ("%s/epiphany/app-%s-%s/%s-%s.desktop",
-                             g_get_user_config_dir (),
-                             id_nonfull,
-                             hash,
-                             id_nonfull,
-                             hash);
-       /* try the new-style location */
-       if (!g_file_test (fn, G_FILE_TEST_EXISTS)) {
-               g_free (fn);
-               fn = g_strdup_printf ("%s/epiphany/app-%s/%s.desktop",
-                                     g_get_user_config_dir (),
-                                     id_nonfull, id_nonfull);
-       }
-       if (g_file_test (fn, G_FILE_TEST_EXISTS)) {
-               gs_app_set_state (app, AS_APP_STATE_INSTALLED);
-               gs_app_add_source_id (app, fn);
-               gs_app_set_management_plugin (app, gs_plugin_get_name (plugin));
-               return TRUE;
-       }
-       gs_app_set_state (app, AS_APP_STATE_AVAILABLE);
-       return TRUE;
+       object_class->dispose = gs_plugin_epiphany_dispose;
+
+       plugin_class->setup_async = gs_plugin_epiphany_setup_async;
+       plugin_class->setup_finish = gs_plugin_epiphany_setup_finish;
+       plugin_class->shutdown_async = gs_plugin_epiphany_shutdown_async;
+       plugin_class->shutdown_finish = gs_plugin_epiphany_shutdown_finish;
+       plugin_class->list_installed_apps_async = gs_plugin_epiphany_list_installed_apps_async;
+       plugin_class->list_installed_apps_finish = gs_plugin_epiphany_list_installed_apps_finish;
 }
 
-gboolean
-gs_plugin_launch (GsPlugin *plugin,
-                 GsApp *app,
-                 GCancellable *cancellable,
-                 GError **error)
+GType
+gs_plugin_query_type (void)
 {
-       /* only process this app if was created by this plugin */
-       if (g_strcmp0 (gs_app_get_management_plugin (app),
-                      gs_plugin_get_name (plugin)) != 0)
-               return TRUE;
-       return gs_plugin_app_launch (plugin, app, error);
+       return GS_TYPE_PLUGIN_EPIPHANY;
 }
diff --git a/plugins/epiphany/gs-plugin-epiphany.h b/plugins/epiphany/gs-plugin-epiphany.h
new file mode 100644
index 000000000..955d0593c
--- /dev/null
+++ b/plugins/epiphany/gs-plugin-epiphany.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2021 Matthew Leeds <mwleeds protonmail com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_PLUGIN_EPIPHANY (gs_plugin_epiphany_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsPluginEpiphany, gs_plugin_epiphany, GS, PLUGIN_EPIPHANY, GsPlugin)
+
+G_END_DECLS
diff --git a/plugins/epiphany/gs-self-test.c b/plugins/epiphany/gs-self-test.c
index 2d2f47888..3615cf2d8 100644
--- a/plugins/epiphany/gs-self-test.c
+++ b/plugins/epiphany/gs-self-test.c
@@ -1,4 +1,5 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
  *
  * Copyright (C) 2013-2017 Richard Hughes <richard hughsie com>
  *
@@ -18,25 +19,26 @@ gs_plugins_epiphany_func (GsPluginLoader *plugin_loader)
        g_autoptr(GError) error = NULL;
        g_autoptr(GsApp) app = NULL;
        g_autoptr(GsPluginJob) plugin_job = NULL;
+       GsPlugin *plugin;
 
        /* no epiphany, abort */
        if (!gs_plugin_loader_get_enabled (plugin_loader, "epiphany"))
                return;
 
        /* a webapp with a local icon */
-       app = gs_app_new ("arachne.desktop");
-       gs_app_set_kind (app, AS_APP_KIND_WEB_APP);
-       plugin_job = gs_plugin_job_newv (GS_PLUGIN_ACTION_REFINE,
-                                        "app", app,
-                                        "refine-flags", GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON,
-                                        NULL);
+       app = gs_app_new ("app.squoosh.webapp.desktop");
+       gs_app_set_kind (app, AS_COMPONENT_KIND_WEB_APP);
+       plugin = gs_plugin_loader_find_plugin (plugin_loader, "epiphany");
+       gs_app_set_management_plugin (app, plugin);
+       plugin_job = gs_plugin_job_refine_new_for_app (app,
+                                                      GS_PLUGIN_REFINE_FLAGS_REQUIRE_ICON);
        ret = gs_plugin_loader_job_action (plugin_loader, plugin_job, NULL, &error);
        gs_test_flush_main_context ();
        g_assert_no_error (error);
-       g_assert (ret);
+       g_assert_true (ret);
 
-       g_assert_cmpint (gs_app_get_state (app), ==, AS_APP_STATE_AVAILABLE);
-       g_assert (gs_app_get_pixbuf (app) != NULL);
+       g_assert_cmpint (gs_app_get_state (app), ==, GS_APP_STATE_AVAILABLE);
+       g_assert_nonnull (gs_app_get_icons (app));
 }
 
 int
@@ -47,7 +49,7 @@ main (int argc, char **argv)
        g_autofree gchar *xml = NULL;
        g_autoptr(GError) error = NULL;
        g_autoptr(GsPluginLoader) plugin_loader = NULL;
-       const gchar *whitelist[] = {
+       const gchar *allowlist[] = {
                "appstream",
                "epiphany",
                "icons",
@@ -58,15 +60,23 @@ main (int argc, char **argv)
        g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
        g_setenv ("GS_XMLB_VERBOSE", "1", TRUE);
 
+       /* Use an icon we already have locally */
        fn = gs_test_get_filename (TESTDATADIR, "icons/hicolor/scalable/org.gnome.Software.svg");
        g_assert (fn != NULL);
        xml = g_strdup_printf ("<?xml version=\"1.0\"?>\n"
-               "<components version=\"0.9\">\n"
+               "<components version=\"0.14\">\n"
                "  <component type=\"webapp\">\n"
-               "    <id>arachne.desktop</id>\n"
-               "    <name>test</name>\n"
-               "    <pkgname>test</pkgname>\n"
+               "    <id>app.squoosh.webapp.desktop</id>\n"
+               "    <metadata_license>CC0-1.0</metadata_license>\n"
+               "    <project_license>Apache-2.0</project_license>\n"
+               "    <name>Squoosh</name>\n"
+               "    <summary>Compress and compare images with different codecs, right in your 
browser</summary>\n"
+               "    <launchable type=\"url\">https://squoosh.app/</launchable>\n"
                "    <icon type=\"remote\">file://%s</icon>\n"
+               "    <categories>\n"
+               "      <category>Utility</category>\n"
+               "    </categories>\n"
+               "    <pkgname>test</pkgname>\n"
                "  </component>\n"
                "  <info>\n"
                "    <scope>user</scope>\n"
@@ -82,12 +92,12 @@ main (int argc, char **argv)
        gs_plugin_loader_add_location (plugin_loader, LOCALPLUGINDIR);
        gs_plugin_loader_add_location (plugin_loader, LOCALPLUGINDIR_CORE);
        ret = gs_plugin_loader_setup (plugin_loader,
-                                     (gchar**) whitelist,
+                                     (gchar**) allowlist,
                                      NULL,
                                      NULL,
                                      &error);
        g_assert_no_error (error);
-       g_assert (ret);
+       g_assert_true (ret);
 
        /* plugin tests go here */
        g_test_add_data_func ("/gnome-software/plugins/epiphany",
diff --git a/plugins/epiphany/meson.build b/plugins/epiphany/meson.build
index 9d57c4d16..f7447c276 100644
--- a/plugins/epiphany/meson.build
+++ b/plugins/epiphany/meson.build
@@ -1,7 +1,15 @@
 cargs = ['-DG_LOG_DOMAIN="GsPluginEpiphany"']
 
+epiphany_generated = gnome.gdbus_codegen(
+  'gs-epiphany-generated',
+  sources : ['org.gnome.Epiphany.WebAppProvider.xml'],
+  interface_prefix : 'org.gnome.Epiphany',
+  namespace : 'GsEphy',
+)
+
 shared_module(
   'gs_plugin_epiphany',
+  epiphany_generated,
   sources : 'gs-plugin-epiphany.c',
   include_directories : [
     include_directories('../..'),
diff --git a/plugins/epiphany/org.gnome.Epiphany.WebAppProvider.xml 
b/plugins/epiphany/org.gnome.Epiphany.WebAppProvider.xml
new file mode 100644
index 000000000..9ff86cd55
--- /dev/null
+++ b/plugins/epiphany/org.gnome.Epiphany.WebAppProvider.xml
@@ -0,0 +1,73 @@
+<!DOCTYPE node PUBLIC
+'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'
+'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>
+<node>
+
+  <!--
+      org.gnome.Epiphany.WebAppProvider:
+      @short_description: Webapp provider interface
+
+      The interface used for handling Epiphany Webapps in GNOME Software.
+
+      This documentation describes version 1 of this interface.
+  -->
+  <interface name="org.gnome.Epiphany.WebAppProvider">
+    <!--
+        version:
+
+        The API version number.
+    -->
+    <property name="version" type="u" access="read"/>
+
+    <!--
+        GetInstalledApps:
+        @desktop_file_ids: An array of .desktop file names, one for each
+          installed web app
+
+        Returns the set of installed Epiphany web applications. The caller can
+        use them with g_desktop_app_info_new() if outside the sandbox.
+    -->
+    <method name="GetInstalledApps">
+      <arg type="as" name="webapps" direction="out" />
+    </method>
+
+    <!--
+        Install:
+        @url: the URL of the web app
+        @name: the human readable name of the web app
+        @install_token: the token acquired via org.freedesktop.portal.InstallDynamicLauncher
+
+        Installs a web app. This interface is expected to be used by trusted
+        system components such as GNOME Software, which can acquire an
+        @install_token by talking to the InstallDynamicLauncher portal. This allows Epiphany
+        to install the web app without user interaction and despite being sandboxed.
+        This is desirable because the user would've already clicked "Install" in
+        Software; they should not have to confirm the operation again in a different
+        app (Epiphany).
+
+        The @install_token must be provided so that Epiphany can complete the
+        installation without a user facing dialog. The icon given to
+        org.freedesktop.portal.InstallDynamicLauncher.RequestInstallToken() will
+        be used, and the name given to that method should match the @name given here.
+    -->
+    <method name="Install">
+      <arg type="s" name="url" direction="in" />
+      <arg type="s" name="name" direction="in" />
+      <arg type="s" name="install_token" direction="in" />
+    </method>
+
+    <!--
+        Uninstall:
+        @desktop_file_id: the filename of the .desktop file for an installed web app
+
+        Uninstalls a web app. Note that the @desktop_file_id is just a filename
+        not a full path, and it's the same one returned by the
+        GetInstalledWebApps() method.
+
+        An error will be returned if the specified web app is not installed.
+    -->
+    <method name="Uninstall">
+      <arg type="s" name="desktop_path" direction="in" />
+    </method>
+  </interface>
+</node>
diff --git a/src/gs-installed-page.c b/src/gs-installed-page.c
index 3817ab01f..afe986050 100644
--- a/src/gs-installed-page.c
+++ b/src/gs-installed-page.c
@@ -310,9 +310,15 @@ gs_installed_page_is_actual_app (GsApp *app)
 {
        if (gs_app_get_description (app) != NULL)
                return TRUE;
+
        /* special snowflake */
        if (g_strcmp0 (gs_app_get_id (app), "google-chrome.desktop") == 0)
                return TRUE;
+
+       /* web apps sometimes don't have descriptions */
+       if (gs_app_get_kind (app) == AS_COMPONENT_KIND_WEB_APP)
+               return TRUE;
+
        g_debug ("%s is not an actual app", gs_app_get_unique_id (app));
        return FALSE;
 }


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