[gnome-software/mwleeds/pwa-plugin: 1/4] Revive webapp support




commit f016b6252b8604663644056c6f9cd5281e8fa86c
Author: Phaedrus Leeds <mwleeds protonmail com>
Date:   Mon Nov 29 14:09:46 2021 -0800

    Revive webapp support
    
    This is a re-implementation of the epiphany plugin (dropped in
    ea5eb222f) in a way that should be more robust and maintainable.
    
    This is the first step toward bringing back webapp support. I am doing
    this as a project sponsored by a GNOME Foundation grant. The goal is to
    include a set of Progressive Web Apps in Software that can be installed
    via Epiphany's web app support (and potentially also via Chromium), to
    expand the selection of apps available to users who might not otherwise
    find them. While Epiphany's web apps do not currently implement support
    for PWA manifests, that is hopefully going to be in scope for this
    project.  While the plan is to only include PWAs in the set hard coded
    into Software, non-PWA web apps installed via Epiphany will also show up
    in Software.
    
    The previous implementation of web apps in Software was dropped because
    it was buggy and users did not like it, since they didn't perceive much
    benefit of using sites as web apps versus using a normal browser and
    were confused by the apps not being native desktop apps. The following
    factors should mitigate or address those issues for the new
    implementation:
    1) We're going to use a D-Bus API provided by Epiphany for enumerating,
       installing, and removing web apps rather than re-implementing those
       functions in Software. This avoids bugs that can occur when the
       implementations are out of sync.
    2) We're going to differentiate web apps from native apps in the UI,
       pending design input on how to do so.
    3) The set of PWAs included with Software will be mostly or entirely
       apps that would not otherwise be available to the user, because they
       are only available as PWAs and not native apps.
    4) Once we have support for PWA manifests in Epiphany, or support for
       Chromium-based web apps which already support manifests, the apps
       should be more featureful and closer to native apps than the current
       web app implementation, e.g. they may work offline to some extent.
    
    (This removes the dependency on epiphany-runtime in the spec file,
    because it's not clear yet if this feature will be enabled by default in
    Fedora and the plan is to make this work with flatpak'd Epiphany which
    doesn't have an analogous way to install epiphany without a desktop
    icon; see https://bugzilla.redhat.com/show_bug.cgi?id=1781359 for
    context.)
    
    The file plugins/epiphany/org.gnome.Epiphany.WebAppProvider.xml comes
    from its canonical source:
    
https://gitlab.gnome.org/GNOME/epiphany/-/blob/master/src/webapp-provider/org.gnome.Epiphany.WebAppProvider.xml

 contrib/gnome-software.spec.in                     |    2 +
 lib/gs-plugin-loader.c                             |    4 +-
 lib/gs-utils.c                                     |   31 +
 lib/gs-utils.h                                     |    1 +
 meson_options.txt                                  |    1 +
 plugins/core/gs-plugin-icons.c                     |    1 +
 plugins/epiphany/gs-epiphany-generated.c           | 1736 ++++++++++++++++++++
 plugins/epiphany/gs-epiphany-generated.h           |  248 +++
 plugins/epiphany/gs-plugin-epiphany.c              |  570 +++++++
 plugins/epiphany/gs-plugin-epiphany.h              |   20 +
 plugins/epiphany/gs-self-test.c                    |  108 ++
 plugins/epiphany/meson.build                       |   60 +
 .../epiphany/org.gnome.Epiphany.WebAppProvider.xml |   73 +
 ....gnome.Software.Plugin.Epiphany.metainfo.xml.in |   11 +
 plugins/meson.build                                |    3 +
 src/gs-installed-page.c                            |    6 +
 16 files changed, 2874 insertions(+), 1 deletion(-)
---
diff --git a/contrib/gnome-software.spec.in b/contrib/gnome-software.spec.in
index f3b35578b..5ea8ec639 100644
--- a/contrib/gnome-software.spec.in
+++ b/contrib/gnome-software.spec.in
@@ -144,11 +144,13 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop
 %{_datadir}/gnome-software/featured-*.svg
 %{_datadir}/gnome-software/featured-*.jpg
 %{_datadir}/metainfo/org.gnome.Software.appdata.xml
+%{_datadir}/metainfo/org.gnome.Software.Plugin.Epiphany.metainfo.xml
 %{_datadir}/metainfo/org.gnome.Software.Plugin.Flatpak.metainfo.xml
 %{_datadir}/metainfo/org.gnome.Software.Plugin.Fwupd.metainfo.xml
 %dir %{gs_plugin_dir}
 %{gs_plugin_dir}/libgs_plugin_appstream.so
 %{gs_plugin_dir}/libgs_plugin_dummy.so
+%{gs_plugin_dir}/libgs_plugin_epiphany.so
 %{gs_plugin_dir}/libgs_plugin_fedora-langpacks.so
 %{gs_plugin_dir}/libgs_plugin_fedora-pkgdb-collections.so
 %{gs_plugin_dir}/libgs_plugin_flatpak.so
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 84e33f769..144f79914 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/meson_options.txt b/meson_options.txt
index 70f62c52b..27a844d50 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -9,6 +9,7 @@ option('fwupd', type : 'boolean', value : true, description : 'enable fwupd supp
 option('flatpak', type : 'boolean', value : true, description : 'enable Flatpak support')
 option('malcontent', type : 'boolean', value : true, description : 'enable parental controls support using 
libmalcontent')
 option('rpm_ostree', type : 'boolean', value : false, description : 'enable rpm-ostree support')
+option('webapps', type : 'boolean', value : true, description : 'enable webapps support')
 option('gudev', type : 'boolean', value : true, description : 'enable GUdev support')
 option('apt', type : 'boolean', value : false, description : 'enable apt: URL handler in the .desktop file')
 option('snap', type : 'boolean', value : false, description : 'enable Snap support')
diff --git a/plugins/core/gs-plugin-icons.c b/plugins/core/gs-plugin-icons.c
index 567fdfa89..72a235ec6 100644
--- a/plugins/core/gs-plugin-icons.c
+++ b/plugins/core/gs-plugin-icons.c
@@ -46,6 +46,7 @@ gs_plugin_icons_init (GsPluginIcons *self)
 {
        /* needs remote icons downloaded */
        gs_plugin_add_rule (GS_PLUGIN (self), GS_PLUGIN_RULE_RUN_AFTER, "appstream");
+       gs_plugin_add_rule (GS_PLUGIN (self), GS_PLUGIN_RULE_RUN_AFTER, "epiphany");
 }
 
 static void
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
new file mode 100644
index 000000000..505669977
--- /dev/null
+++ b/plugins/epiphany/gs-plugin-epiphany.c
@@ -0,0 +1,570 @@
+/* -*- 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+
+ */
+
+#include <config.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:
+ * This plugin uses Epiphany to install, launch, and uninstall web applications.
+ *
+ * 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.
+ */
+
+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)
+{
+       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 (GS_PLUGIN (self), "org.gnome.Software.Plugin.Epiphany");
+
+       /* need help from 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)
+{
+       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, plugin);
+       }
+
+       if (gs_app_get_state (app) == GS_APP_STATE_UNKNOWN)
+               gs_app_set_state (app, GS_APP_STATE_AVAILABLE);
+}
+
+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)
+{
+       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)
+{
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
+       const char *url;
+       const char *name;
+       const char *icon_format;
+       g_autofree char *token = NULL;
+       GPtrArray *icons;
+       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;
+
+       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;
+       }
+       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;
+       }
+       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;
+               }
+       }
+       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;
+       }
+
+       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;
+       }
+
+       /* Then pass the token to Epiphany which will use xdg-desktop-portal to
+        * complete the installation
+        */
+       g_variant_get (token_v, "(s)", &token);
+       if (!gs_ephy_web_app_provider_call_install_sync (self->epiphany_proxy,
+                                                        url, name, token,
+                                                        cancellable,
+                                                        error)) {
+               gs_epiphany_error_convert (error);
+               gs_app_set_state_recover (app);
+               return FALSE;
+       }
+       gs_app_set_state (app, GS_APP_STATE_INSTALLED);
+
+       return TRUE;
+}
+
+gboolean
+gs_plugin_app_remove (GsPlugin      *plugin,
+                     GsApp         *app,
+                     GCancellable  *cancellable,
+                     GError       **error)
+{
+       GsPluginEpiphany *self = GS_PLUGIN_EPIPHANY (plugin);
+
+       if (!gs_app_has_management_plugin (app, plugin))
+               return TRUE;
+
+       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, GS_APP_STATE_AVAILABLE);
+
+       return TRUE;
+}
+
+gboolean
+gs_plugin_launch (GsPlugin      *plugin,
+                 GsApp         *app,
+                 GCancellable  *cancellable,
+                 GError       **error)
+{
+       if (!gs_app_has_management_plugin (app, plugin))
+               return TRUE;
+
+       return gs_plugin_app_launch (plugin, app, error);
+}
+
+static void
+gs_plugin_epiphany_class_init (GsPluginEpiphanyClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GsPluginClass *plugin_class = GS_PLUGIN_CLASS (klass);
+
+       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;
+}
+
+GType
+gs_plugin_query_type (void)
+{
+       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
new file mode 100644
index 000000000..3615cf2d8
--- /dev/null
+++ b/plugins/epiphany/gs-self-test.c
@@ -0,0 +1,108 @@
+/* -*- 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>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include "config.h"
+
+#include "gnome-software-private.h"
+
+#include "gs-test.h"
+
+static void
+gs_plugins_epiphany_func (GsPluginLoader *plugin_loader)
+{
+       gboolean ret;
+       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 ("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_true (ret);
+
+       g_assert_cmpint (gs_app_get_state (app), ==, GS_APP_STATE_AVAILABLE);
+       g_assert_nonnull (gs_app_get_icons (app));
+}
+
+int
+main (int argc, char **argv)
+{
+       gboolean ret;
+       g_autofree gchar *fn = NULL;
+       g_autofree gchar *xml = NULL;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsPluginLoader) plugin_loader = NULL;
+       const gchar *allowlist[] = {
+               "appstream",
+               "epiphany",
+               "icons",
+               NULL
+       };
+
+       g_test_init (&argc, &argv, NULL);
+       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.14\">\n"
+               "  <component type=\"webapp\">\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"
+               "  </info>\n"
+               "</components>\n", fn);
+       g_setenv ("GS_SELF_TEST_APPSTREAM_XML", xml, TRUE);
+
+       /* only critical and error are fatal */
+       g_log_set_fatal_mask (NULL, G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL);
+
+       /* we can only load this once per process */
+       plugin_loader = gs_plugin_loader_new ();
+       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**) allowlist,
+                                     NULL,
+                                     NULL,
+                                     &error);
+       g_assert_no_error (error);
+       g_assert_true (ret);
+
+       /* plugin tests go here */
+       g_test_add_data_func ("/gnome-software/plugins/epiphany",
+                             plugin_loader,
+                             (GTestDataFunc) gs_plugins_epiphany_func);
+
+       return g_test_run ();
+}
diff --git a/plugins/epiphany/meson.build b/plugins/epiphany/meson.build
new file mode 100644
index 000000000..f7447c276
--- /dev/null
+++ b/plugins/epiphany/meson.build
@@ -0,0 +1,60 @@
+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('../..'),
+    include_directories('../../lib'),
+  ],
+  install : true,
+  install_dir: plugin_dir,
+  c_args : cargs,
+  dependencies : plugin_libs,
+  link_with : [
+    libgnomesoftware
+  ]
+)
+metainfo = 'org.gnome.Software.Plugin.Epiphany.metainfo.xml'
+
+i18n.merge_file(
+  input: metainfo + '.in',
+  output: metainfo,
+  type: 'xml',
+  po_dir: join_paths(meson.source_root(), 'po'),
+  install: true,
+  install_dir: join_paths(get_option('datadir'), 'metainfo')
+)
+
+if get_option('tests')
+  cargs += ['-DLOCALPLUGINDIR="' + meson.current_build_dir() + '"']
+  cargs += ['-DLOCALPLUGINDIR_CORE="' + meson.current_build_dir() + '/../core"']
+  cargs += ['-DTESTDATADIR="' + join_paths(meson.current_source_dir(), '..', '..', 'data') + '"']
+  e = executable(
+    'gs-self-test-epiphany',
+    compiled_schemas,
+    sources : [
+      'gs-self-test.c'
+    ],
+    include_directories : [
+      include_directories('../..'),
+      include_directories('../../lib'),
+    ],
+    dependencies : [
+      plugin_libs,
+    ],
+    link_with : [
+      libgnomesoftware
+    ],
+    c_args : cargs,
+  )
+  test('gs-self-test-epiphany', e, suite: ['plugins', 'epiphany'], env: test_env)
+endif
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/plugins/epiphany/org.gnome.Software.Plugin.Epiphany.metainfo.xml.in 
b/plugins/epiphany/org.gnome.Software.Plugin.Epiphany.metainfo.xml.in
new file mode 100644
index 000000000..626381f90
--- /dev/null
+++ b/plugins/epiphany/org.gnome.Software.Plugin.Epiphany.metainfo.xml.in
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2013-2016 Richard Hughes <richard hughsie com> -->
+<component type="addon">
+  <id>org.gnome.Software.Plugin.Epiphany</id>
+  <extends>org.gnome.Software.desktop</extends>
+  <name>Web Apps Support</name>
+  <summary>Run popular web applications in a browser</summary>
+  <metadata_license>CC0-1.0</metadata_license>
+  <project_license>GPL-2.0+</project_license>
+  <update_contact>mwleeds_at_protonmail.com</update_contact>
+</component>
diff --git a/plugins/meson.build b/plugins/meson.build
index 01fc42f17..711b488e2 100644
--- a/plugins/meson.build
+++ b/plugins/meson.build
@@ -38,3 +38,6 @@ endif
 if get_option('snap')
   subdir('snap')
 endif
+if get_option('webapps')
+  subdir('epiphany')
+endif
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]