diff -pruN 1.16.0-2/debian/changelog 1.16.0-2ubuntu1/debian/changelog
--- 1.16.0-2/debian/changelog	2023-01-10 11:31:45.000000000 +0000
+++ 1.16.0-2ubuntu1/debian/changelog	2023-01-25 18:33:03.000000000 +0000
@@ -1,3 +1,16 @@
+xdg-desktop-portal (1.16.0-2ubuntu1) lunar; urgency=medium
+
+  * Merge from Debian. Remaining changes:
+    - Import https://github.com/flatpak/xdg-desktop-portal/pull/705 as a
+      distro-patch (add a portal for managing WebExtensions native messaging
+      servers, LP: #1968215)
+      + debian/patches/webextensions-portal.patch
+  * d/p/webextensions-portal.patch: updated
+  * Keep alternative dependency to libgdk-pixbuf2.0-dev for easier backports
+    to Ubuntu 20.04 LTS or 18.04 LTS
+
+ -- Nathan Pratta Teodosio <nathan.teodosio@canonical.com>  Wed, 25 Jan 2023 15:33:03 -0300
+
 xdg-desktop-portal (1.16.0-2) unstable; urgency=medium
 
   * d/patches: Add post-release bug fixes from upstream
@@ -9,6 +22,17 @@ xdg-desktop-portal (1.16.0-2) unstable;
 
  -- Simon McVittie <smcv@debian.org>  Tue, 10 Jan 2023 11:31:45 +0000
 
+xdg-desktop-portal (1.16.0-1ubuntu1) lunar; urgency=medium
+
+  * Merge from Debian. Remaining changes:
+    - Import https://github.com/flatpak/xdg-desktop-portal/pull/705 as a
+      distro-patch (add a portal for managing WebExtensions native messaging
+      servers, LP: #1968215)
+      + debian/patches/webextensions-portal.patch
+  * Update webextensions-portal.patch with latest proposed version
+
+ -- Jeremy Bicha <jbicha@ubuntu.com>  Thu, 15 Dec 2022 10:39:43 -0500
+
 xdg-desktop-portal (1.16.0-1) unstable; urgency=medium
 
   * New upstream release
@@ -55,6 +79,17 @@ xdg-desktop-portal (1.15.0-2) unstable;
 
  -- Simon McVittie <smcv@debian.org>  Thu, 15 Sep 2022 19:20:27 +0100
 
+xdg-desktop-portal (1.15.0-1ubuntu1) kinetic; urgency=medium
+
+  * Merge from Debian. Remaining changes:
+    - Import https://github.com/flatpak/xdg-desktop-portal/pull/705 as a
+      distro-patch (add a portal for managing WebExtensions native messaging
+      servers, LP: #1968215)
+      + debian/patches/webextensions-portal.patch
+  * Update webextensions-portal.patch with latest proposed version
+
+ -- Jeremy Bicha <jbicha@ubuntu.com>  Mon, 15 Aug 2022 06:59:02 -0400
+
 xdg-desktop-portal (1.15.0-1) experimental; urgency=medium
 
   * New upstream development release
@@ -81,6 +116,16 @@ xdg-desktop-portal (1.14.5-1) unstable;
 
  -- Simon McVittie <smcv@debian.org>  Tue, 19 Jul 2022 18:58:07 +0100
 
+xdg-desktop-portal (1.14.4-1ubuntu1) kinetic; urgency=medium
+
+  * Merge from Debian. Remaining changes:
+    - Import https://github.com/flatpak/xdg-desktop-portal/pull/705 as a
+      distro-patch (add a portal for managing WebExtensions native messaging
+      servers, LP: #1968215)
+      + debian/patches/webextensions-portal.patch
+
+ -- Olivier Tilloy <olivier.tilloy@canonical.com>  Mon, 13 Jun 2022 11:21:47 +0200
+
 xdg-desktop-portal (1.14.4-1) unstable; urgency=medium
 
   * New upstream stable release
@@ -93,6 +138,37 @@ xdg-desktop-portal (1.14.3-1) unstable;
 
  -- Simon McVittie <smcv@debian.org>  Fri, 15 Apr 2022 16:30:43 +0100
 
+xdg-desktop-portal (1.14.3-0ubuntu3) kinetic; urgency=medium
+
+  * Update the native messaging portal patch to pick up a new commit
+    - debian/patches/webextensions-portal.patch
+
+ -- Olivier Tilloy <olivier.tilloy@canonical.com>  Wed, 11 May 2022 11:06:32 +0200
+
+xdg-desktop-portal (1.14.3-0ubuntu2) jammy; urgency=medium
+
+  * New upstream stable release
+    - Fixes crash with Teamviewer's experimental Wayland support
+      (LP: #1966945)
+
+ -- Jeremy Bicha <jbicha@ubuntu.com>  Fri, 15 Apr 2022 15:17:26 -0400
+
+xdg-desktop-portal (1.14.2-1ubuntu2) jammy; urgency=medium
+
+  * Update the native messaging portal patch to pick up new commits
+    - debian/patches/webextensions-portal.patch
+
+ -- Olivier Tilloy <olivier.tilloy@canonical.com>  Mon, 11 Apr 2022 18:47:05 +0200
+
+xdg-desktop-portal (1.14.2-1ubuntu1) jammy; urgency=medium
+
+  * Import https://github.com/flatpak/xdg-desktop-portal/pull/705 as a
+    distro-patch (add a portal for managing WebExtensions native messaging
+    servers, LP: #1968215)
+    - debian/patches/webextensions-portal.patch
+
+ -- Olivier Tilloy <olivier.tilloy@canonical.com>  Fri, 08 Apr 2022 11:16:45 +0200
+
 xdg-desktop-portal (1.14.2-1) unstable; urgency=medium
 
   * New upstream release
diff -pruN 1.16.0-2/debian/control 1.16.0-2ubuntu1/debian/control
--- 1.16.0-2/debian/control	2023-01-10 11:31:45.000000000 +0000
+++ 1.16.0-2ubuntu1/debian/control	2023-01-25 18:33:03.000000000 +0000
@@ -1,7 +1,8 @@
 Source: xdg-desktop-portal
 Section: admin
 Priority: optional
-Maintainer: Utopia Maintenance Team <pkg-utopia-maintainers@lists.alioth.debian.org>
+Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
+XSBC-Original-Maintainer: Utopia Maintenance Team <pkg-utopia-maintainers@lists.alioth.debian.org>
 Uploaders:
  Simon McVittie <smcv@debian.org>,
 Build-Depends:
@@ -10,7 +11,7 @@ Build-Depends:
  geoclue-2.0 <!nocheck>,
  fuse3 <!nocheck>,
  libcap2-bin <!nocheck>,
- libgdk-pixbuf-2.0-dev,
+ libgdk-pixbuf-2.0-dev | libgdk-pixbuf2.0-dev,
  libgeoclue-2-dev (>= 2.5.2),
  libflatpak-dev (>= 1.5.0),
  libfuse3-dev (>= 3.10.0),
@@ -28,8 +29,10 @@ Build-Depends:
 Rules-Requires-Root: no
 Standards-Version: 4.6.1
 Homepage: https://github.com/flatpak/xdg-desktop-portal
-Vcs-Git: https://salsa.debian.org/debian/xdg-desktop-portal.git
-Vcs-Browser: https://salsa.debian.org/debian/xdg-desktop-portal
+XS-Debian-Vcs-Git: https://salsa.debian.org/debian/xdg-desktop-portal.git
+XS-Debian-Vcs-Browser: https://salsa.debian.org/debian/xdg-desktop-portal
+Vcs-Git: https://salsa.debian.org/debian/xdg-desktop-portal.git -b ubuntu/master
+Vcs-Browser: https://salsa.debian.org/debian/xdg-desktop-portaltree/ubuntu/master
 
 Package: xdg-desktop-portal
 Architecture: linux-any
diff -pruN 1.16.0-2/debian/gbp.conf 1.16.0-2ubuntu1/debian/gbp.conf
--- 1.16.0-2/debian/gbp.conf	2023-01-10 11:31:45.000000000 +0000
+++ 1.16.0-2ubuntu1/debian/gbp.conf	2023-01-25 18:33:03.000000000 +0000
@@ -1,7 +1,8 @@
 [DEFAULT]
 pristine-tar = True
 compression = xz
-debian-branch = debian/master
+debian-branch = ubuntu/master
+debian-tag = ubuntu/%(version)s
 upstream-branch = upstream/latest
 patch-numbers = False
 upstream-vcs-tag = %(version)s
diff -pruN 1.16.0-2/debian/patches/series 1.16.0-2ubuntu1/debian/patches/series
--- 1.16.0-2/debian/patches/series	2023-01-10 11:31:45.000000000 +0000
+++ 1.16.0-2ubuntu1/debian/patches/series	2023-01-25 18:33:03.000000000 +0000
@@ -1,3 +1,4 @@
+webextensions-portal.patch
 file-chooser-Set-writable-flag-correctly.patch
 tests-List-lists-of-sources-one-per-line-in-alphabetical-.patch
 tests-Add-missing-dependency-on-permission-store-GDBus-he.patch
diff -pruN 1.16.0-2/debian/patches/webextensions-portal.patch 1.16.0-2ubuntu1/debian/patches/webextensions-portal.patch
--- 1.16.0-2/debian/patches/webextensions-portal.patch	1970-01-01 00:00:00.000000000 +0000
+++ 1.16.0-2ubuntu1/debian/patches/webextensions-portal.patch	2023-01-25 18:33:03.000000000 +0000
@@ -0,0 +1,977 @@
+From: James Henstridge <james@jamesh.id.au>
+Date: Tue, 1 Feb 2022 20:12:49 +0800
+Subject: [PATCH 01/10] webextensions: add a portal for managing WebExtensions
+ native messaging servers.
+
+This is intended to provide a way for a confined web browser to start
+native code helpers for their extensions. At present it can start the
+servers installed on the host system. But in future this could be
+extended to cover sandboxed native messaging servers too.
+
+https://github.com/flatpak/xdg-desktop-portal/pull/705
+---
+ Makefile.am.inc                               |   1 +
+ data/meson.build                              |   1 +
+ data/org.freedesktop.portal.WebExtensions.xml | 113 +++++
+ src/Makefile.am.inc                           |   2 +
+ src/meson.build                               |   1 +
+ src/request.c                                 |  12 +
+ src/webextensions.c                           | 641 ++++++++++++++++++++++++++
+ src/webextensions.h                           |  24 +
+ src/xdg-desktop-portal.c                      |  10 +
+ src/xdp-utils.c                               |  23 +
+ src/xdp-utils.h                               |   1 +
+ 11 files changed, 829 insertions(+)
+ create mode 100644 data/org.freedesktop.portal.WebExtensions.xml
+ create mode 100644 src/webextensions.c
+ create mode 100644 src/webextensions.h
+
+diff --git a/Makefile.am.inc b/Makefile.am.inc
+index 7be4c4f..8a09acf 100644
+--- a/Makefile.am.inc
++++ b/Makefile.am.inc
+@@ -28,6 +28,7 @@ PORTAL_IFACE_FILES =\
+ 	$(top_srcdir)/data/org.freedesktop.portal.Settings.xml \
+ 	$(top_srcdir)/data/org.freedesktop.portal.Trash.xml \
+ 	$(top_srcdir)/data/org.freedesktop.portal.Wallpaper.xml \
++	$(top_srcdir)/data/org.freedesktop.portal.WebExtensions.xml \
+ 	$(NULL)
+ 
+ PORTAL_IMPL_IFACE_FILES =\
+diff --git a/data/meson.build b/data/meson.build
+index 489479f..bb1a64f 100644
+--- a/data/meson.build
++++ b/data/meson.build
+@@ -31,6 +31,7 @@ portal_sources = files(
+   'org.freedesktop.portal.Settings.xml',
+   'org.freedesktop.portal.Trash.xml',
+   'org.freedesktop.portal.Wallpaper.xml',
++  'org.freedesktop.portal.WebExtensions.xml',
+ )
+ 
+ portal_impl_sources = files(
+diff --git a/data/org.freedesktop.portal.WebExtensions.xml b/data/org.freedesktop.portal.WebExtensions.xml
+new file mode 100644
+index 0000000..c2f7b3c
+--- /dev/null
++++ b/data/org.freedesktop.portal.WebExtensions.xml
+@@ -0,0 +1,113 @@
++<?xml version="1.0"?>
++<!--
++ Copyright (C) 2022 Canonical Ltd
++
++ This library is free software; you can redistribute it and/or
++ modify it under the terms of the GNU Lesser General Public
++ License as published by the Free Software Foundation; either
++ version 2 of the License, or (at your option) any later version.
++
++ This library is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ Lesser General Public License for more details.
++
++ You should have received a copy of the GNU Lesser General Public
++ License along with this library. If not, see <http://www.gnu.org/licenses/>.
++-->
++
++<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
++  <!--
++      org.freedesktop.portal.WebExtensions:
++      @short_description: WebExtensions portal
++
++      The WebExtensions portal allows sandboxed web browsers to start
++      native messaging servers installed on the host system.
++
++      This documentation describes version 1 of this interface.
++  -->
++  <interface name="org.freedesktop.portal.WebExtensions">
++    <!--
++        CreateSession:
++        @options: Vardict with optional further information
++        @session_handle: Object path for the #org.freedesktop.portal.Session created by this call.
++
++        Create a native messaging session. A successfully created
++        session can at any time be closed using
++        org.freedesktop.portal.Session::Close, or may at any time be
++        closed by the portal implementation, which will be signalled
++        via org.freedesktop.portal.Session::Closed.
++
++        Supported keys in the @options vardict include:
++        <variablelist>
++          <varlistentry>
++            <term>session_handle_token s</term>
++            <listitem><para>
++              A string that will be used as the last element of the session handle. Must be a valid
++              object path element. See the #org.freedesktop.portal.Session documentation for
++              more information about the session handle.
++            </para></listitem>
++          </varlistentry>
++        </variablelist>
++    -->
++    <method name="CreateSession">
++      <arg type="a{sv}" name="options" direction="in"/>
++      <arg type="o" name="session_handle" direction="out"/>
++    </method>
++    <!--
++        Start:
++        @session_handle: Object path for the #org.freedesktop.portal.Session objetc
++        @name: name of the native messaging server
++        @extension_or_origin: extension ID or origin URI identifying the extension
++        @options: Vardict with optional further information
++        @handle: Object path for the #org.freedesktop.portal.Request object representing this call
++
++        Start the named native messaging server. The caller must
++        indicate the requesting web extension (either by extension ID
++        for Firefox, or origin URI for Chrome), which will be matched
++        against the server's access control list.
++
++        If the server can't be started, or invalid data is provided,
++        the session will be closed.
++
++        Supported keys in the @options vardict include:
++        <variablelist>
++          <varlistentry>
++            <term>handle_token s</term>
++            <listitem><para>
++              A string that will be used as the last element of the @handle. Must be a valid
++              object path element. See the #org.freedesktop.portal.Request documentation for
++              more information about the @handle.
++            </para></listitem>
++          </varlistentry>
++        </variablelist>
++    -->
++    <method name="Start">
++      <arg type="o" name="session_handle" direction="in"/>
++      <arg type="s" name="name" direction="in"/>
++      <arg type="s" name="extension_or_origin" direction="in"/>
++      <arg type="a{sv}" name="options" direction="in"/>
++      <arg type="o" name="handle" direction="out"/>
++    </method>
++    <!--
++        GetPipes:
++        @session_handle: Object path for the #org.freedesktop.portal.Session object
++        @options: Vardict with optional further information
++        @stdin: File descriptor representing the server's stdin.
++        @stdout: File descriptor representing the server's stdout.
++        @stderr: File descriptor representing the server's stderr.
++
++        Retrieve file descriptors for the native messaging server
++        identified by the session.
++    -->
++    <method name="GetPipes">
++      <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
++      <arg type="o" name="session_handle" direction="in"/>
++      <arg type="a{sv}" name="options" direction="in"/>
++      <arg type="h" name="stdin" direction="out"/>
++      <arg type="h" name="stdout" direction="out"/>
++      <arg type="h" name="stderr" direction="out"/>
++    </method>
++    <property name="version" type="u" access="read"/>
++  </interface>
++</node>
+diff --git a/src/Makefile.am.inc b/src/Makefile.am.inc
+index 86a8de0..b7ca796 100644
+--- a/src/Makefile.am.inc
++++ b/src/Makefile.am.inc
+@@ -130,6 +130,8 @@ xdg_desktop_portal_SOURCES = \
+ 	src/portal-impl.c		\
+ 	src/global-shortcuts.h		\
+ 	src/global-shortcuts.c		\
++	src/webextensions.h             \
++	src/webextensions.c             \
+ 	$(NULL)
+ 
+ if HAVE_LIBSYSTEMD
+diff --git a/src/meson.build b/src/meson.build
+index 24227fe..9d46f3d 100644
+--- a/src/meson.build
++++ b/src/meson.build
+@@ -67,6 +67,7 @@ xdg_desktop_portal_sources = files(
+   'settings.c',
+   'trash.c',
+   'wallpaper.c',
++  'webextensions.c',
+   'xdg-desktop-portal.c',
+   'xdp-utils.c',
+ )
+diff --git a/src/request.c b/src/request.c
+index 5bd5016..9191911 100644
+--- a/src/request.c
++++ b/src/request.c
+@@ -343,6 +343,18 @@ get_token (GDBusMethodInvocation *invocation)
+                      interface, method, G_STRLOC);
+         }
+     }
++  else if (strcmp (interface, "org.freedesktop.portal.WebExtensions") == 0)
++    {
++      if (strcmp (method, "Start") == 0)
++        {
++          options = g_variant_get_child_value (parameters, 3);
++        }
++      else
++        {
++          g_warning ("Support for %s::%s missing in %s",
++                     interface, method, G_STRLOC);
++        }
++    }
+   else
+     {
+       g_print ("Support for %s missing in " G_STRLOC, interface);
+diff --git a/src/webextensions.c b/src/webextensions.c
+new file mode 100644
+index 0000000..c308ce1
+--- /dev/null
++++ b/src/webextensions.c
+@@ -0,0 +1,641 @@
++/*
++ * Copyright © 2022 Canonical Ltd
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
++ *
++ */
++
++#include "config.h"
++
++#include <stdint.h>
++#include <sys/types.h>
++#include <sys/wait.h>
++#include <glib/gi18n.h>
++#include <gio/gunixfdlist.h>
++#include <json-glib/json-glib.h>
++
++#include "session.h"
++#include "webextensions.h"
++#include "request.h"
++#include "permissions.h"
++#include "xdp-dbus.h"
++#include "xdp-impl-dbus.h"
++#include "xdp-utils.h"
++
++#define PERMISSION_TABLE "webextensions"
++
++typedef struct _WebExtensions WebExtensions;
++typedef struct _WebExtensionsClass WebExtensionsClass;
++
++struct _WebExtensions
++{
++  XdpDbusWebExtensionsSkeleton parent_instance;
++};
++
++struct _WebExtensionsClass
++{
++  XdpDbusWebExtensionsSkeletonClass parent_class;
++};
++
++static XdpDbusImplAccess *access_impl;
++static WebExtensions *web_extensions;
++
++GType web_extensions_get_type (void);
++static void web_extensions_iface_init (XdpDbusWebExtensionsIface *iface);
++
++G_DEFINE_TYPE_WITH_CODE (WebExtensions, web_extensions, XDP_DBUS_TYPE_WEB_EXTENSIONS_SKELETON,
++                         G_IMPLEMENT_INTERFACE (XDP_DBUS_TYPE_WEB_EXTENSIONS,
++                                                web_extensions_iface_init));
++
++typedef enum _WebExtensionsSessionState
++{
++  WEB_EXTENSIONS_SESSION_STATE_INIT,
++  WEB_EXTENSIONS_SESSION_STATE_STARTING,
++  WEB_EXTENSIONS_SESSION_STATE_STARTED,
++  WEB_EXTENSIONS_SESSION_STATE_CLOSED,
++} WebExtensionsSessionState;
++
++typedef struct _WebExtensionsSession
++{
++  Session parent;
++
++  WebExtensionsSessionState state;
++
++  GPid child_pid;
++  guint child_watch_id;
++
++  int standard_input;
++  int standard_output;
++  int standard_error;
++} WebExtensionsSession;
++
++typedef struct _WebExtensionsSessionClass
++{
++  SessionClass parent_class;
++} WebExtensionsSessionClass;
++
++GType web_extensions_session_get_type (void);
++
++G_DEFINE_TYPE (WebExtensionsSession, web_extensions_session, session_get_type ());
++
++static void
++web_extensions_session_init (WebExtensionsSession *session)
++{
++  session->child_pid = -1;
++  session->child_watch_id = 0;
++
++  session->standard_input = -1;
++  session->standard_output = -1;
++  session->standard_error = -1;
++}
++
++static void
++web_extensions_session_close (Session *session)
++{
++  WebExtensionsSession *we_session = (WebExtensionsSession *)session;
++
++  if (we_session->state == WEB_EXTENSIONS_SESSION_STATE_CLOSED) return;
++
++  we_session->state = WEB_EXTENSIONS_SESSION_STATE_CLOSED;
++  if (we_session->child_watch_id != 0)
++    {
++      g_source_remove (we_session->child_watch_id);
++      we_session->child_watch_id = 0;
++    }
++
++  if (we_session->child_pid > 0)
++    {
++      kill (we_session->child_pid, SIGTERM);
++      waitpid (we_session->child_pid, NULL, 0);
++      g_spawn_close_pid (we_session->child_pid);
++      we_session->child_pid = -1;
++    }
++
++  if (we_session->standard_input >= 0)
++    {
++      close (we_session->standard_input);
++      we_session->standard_input = -1;
++    }
++  if (we_session->standard_output >= 0)
++    {
++      close (we_session->standard_output);
++      we_session->standard_output = -1;
++    }
++  if (we_session->standard_error >= 0)
++    {
++      close (we_session->standard_error);
++      we_session->standard_error = -1;
++    }
++}
++
++static void
++web_extensions_session_finalize (GObject *object)
++{
++  Session *session = (Session *)object;
++
++  web_extensions_session_close (session);
++  G_OBJECT_CLASS (web_extensions_session_parent_class)->finalize (object);
++}
++
++static void
++web_extensions_session_class_init (WebExtensionsSessionClass *klass)
++{
++  GObjectClass *object_class;
++  SessionClass *session_class;
++
++  object_class = G_OBJECT_CLASS (klass);
++  object_class->finalize = web_extensions_session_finalize;
++
++  session_class = (SessionClass *)klass;
++  session_class->close = web_extensions_session_close;
++}
++
++static WebExtensionsSession *
++web_extensions_session_new (GVariant *options,
++                            Call *call,
++                            GDBusConnection *connection,
++                            GError **error)
++{
++  Session *session;
++  const char *session_token;
++
++  session_token = lookup_session_token (options);
++  session = g_initable_new (web_extensions_session_get_type (), NULL, error,
++                            "sender", call->sender,
++                            "app-id", xdp_app_info_get_id (call->app_info),
++                            "token", session_token,
++                            "connection", connection,
++                            NULL);
++
++  if (session)
++    g_debug ("webextensions session owned by '%s' created", session->sender);
++
++  return (WebExtensionsSession *)session;
++}
++
++static gboolean
++handle_create_session (XdpDbusWebExtensions *object,
++                       GDBusMethodInvocation *invocation,
++                       GVariant *arg_options)
++{
++  Call *call = call_from_invocation (invocation);
++  GDBusConnection *connection = g_dbus_method_invocation_get_connection (invocation);
++  g_autoptr(GError) error = NULL;
++  Session *session;
++
++  if (!xdp_app_info_is_web_browser (call->app_info))
++    {
++      g_dbus_method_invocation_return_error (invocation,
++                                             G_DBUS_ERROR,
++                                             G_DBUS_ERROR_ACCESS_DENIED,
++                                             "cannot start WebExtensions native messaging servers");
++      return TRUE;
++    }
++
++  session = (Session *)web_extensions_session_new (arg_options, call, connection, &error);
++  if (!session)
++    {
++      g_dbus_method_invocation_return_gerror (invocation, error);
++      return TRUE;
++    }
++  if (!session_export (session, &error))
++    {
++      g_dbus_method_invocation_return_gerror (invocation, error);
++      session_close (session, FALSE);
++      return TRUE;
++    }
++  session_register (session);
++
++  xdp_dbus_web_extensions_complete_create_session (object, invocation, session->id);
++
++  return TRUE;
++}
++
++static void
++on_server_exited (GPid pid,
++                  gint status,
++                  gpointer user_data)
++{
++  Session *session = user_data;
++  WebExtensionsSession *we_session = (WebExtensionsSession *)session;
++
++  SESSION_AUTOLOCK (session);
++  we_session->child_pid = -1;
++  we_session->child_watch_id = 0;
++  session_close (session, TRUE);
++}
++
++static gboolean
++array_contains (JsonArray *array,
++                const char *value)
++{
++  guint length, i;
++
++  if (array == NULL)
++    return FALSE;
++
++  length = json_array_get_length (array);
++  for (i = 0; i < length; i++)
++    {
++      const char *element = json_array_get_string_element (array, i);
++      if (g_strcmp0 (element, value) == 0)
++        return TRUE;
++    }
++  return FALSE;
++}
++
++static GStrv
++get_manifest_search_path (void)
++{
++  const char *hosts_path_str;
++  g_autoptr(GPtrArray) search_path = NULL;
++
++  hosts_path_str = g_getenv ("XDG_DESKTOP_PORTAL_WEB_EXTENSIONS_PATH");
++  if (hosts_path_str != NULL)
++    return g_strsplit (hosts_path_str, ":", -1);
++
++  /* By default, use the native messaging search paths of Firefox,
++   * Chrome, and Chromium, as documented here:
++   *
++   * https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#manifest_location
++   * https://developer.chrome.com/docs/apps/nativeMessaging/#native-messaging-host-location
++   */
++
++  search_path = g_ptr_array_new_with_free_func (g_free);
++  /* Add per-user directories */
++  g_ptr_array_add (search_path, g_build_filename (g_get_home_dir (), ".mozilla", "native-messaging-hosts", NULL));
++  g_ptr_array_add (search_path, g_build_filename (g_get_user_config_dir (), "google-chrome", "NativeMessagingHosts", NULL));
++  g_ptr_array_add (search_path, g_build_filename (g_get_user_config_dir (), "chromium", "NativeMessagingHosts", NULL));
++
++  /* Add system wide directories */
++  g_ptr_array_add (search_path, g_strdup ("/usr/lib/mozilla/native-messaging-hosts"));
++  g_ptr_array_add (search_path, g_strdup ("/etc/opt/chrome/native-messaging-hosts"));
++  g_ptr_array_add (search_path, g_strdup ("/etc/chromium/native-messaging-hosts"));
++
++  g_ptr_array_add (search_path, NULL);
++  return (GStrv)g_ptr_array_free (g_steal_pointer (&search_path), FALSE);
++}
++
++static char *
++find_server (const char *server_name,
++             const char *extension_or_origin,
++             char **server_description,
++             GError **error)
++{
++  g_auto(GStrv) search_path;
++  g_autoptr(JsonParser) parser = NULL;
++  int i;
++
++  search_path = get_manifest_search_path ();
++  parser = json_parser_new ();
++
++  for (i = 0; search_path[i] != NULL; i++)
++    {
++      g_autoptr(GFile) dir = NULL;
++      g_autoptr(GFileEnumerator) enumerator = NULL;
++      dir = g_file_new_for_path (search_path[i]);
++      enumerator = g_file_enumerate_children (dir, "*", G_FILE_QUERY_INFO_NONE, NULL, NULL);
++      if (enumerator == NULL)
++        continue;
++
++      while (TRUE)
++        {
++          g_autoptr(GFileInfo) info = g_file_enumerator_next_file (enumerator, NULL, NULL);
++          const char *name;
++          g_autofree char *metadata_filename = NULL;
++          JsonObject *metadata_root;
++
++          if (info == NULL)
++            break;
++          name = g_file_info_get_name (info);
++          if (!g_str_has_suffix (name, ".json"))
++            continue;
++
++          metadata_filename = g_build_filename (search_path[i], name, NULL);
++          if (!json_parser_load_from_file (parser, metadata_filename, error))
++            return NULL;
++
++          metadata_root = json_node_get_object (json_parser_get_root (parser));
++
++          /* Skip if this isn't the requested native messaging server */
++          if (g_strcmp0 (json_object_get_string_member (metadata_root, "name"), server_name) != 0)
++            continue;
++
++          /* Skip if this is not a "stdio" type native messaging server */
++          if (g_strcmp0 (json_object_get_string_member (metadata_root, "type"), "stdio") != 0)
++            continue;
++
++          /* Skip if this server isn't available to the extension. Note that  */
++          if (!array_contains (json_object_get_array_member (metadata_root, "allowed_extensions"), extension_or_origin) &&
++              !array_contains (json_object_get_array_member (metadata_root, "allowed_origins"), extension_or_origin))
++            continue;
++
++          /* Server matches: return its executable path and description */
++          if (server_description != NULL)
++            *server_description = g_strdup (json_object_get_string_member (metadata_root, "description"));
++          return g_strdup (json_object_get_string_member (metadata_root, "path"));
++        }
++    }
++
++  g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_NOT_FOUND, "cannot find native messaging server");
++  return NULL;
++}
++
++static void
++handle_start_in_thread (GTask *task,
++                        gpointer source_object,
++                        gpointer task_data,
++                        GCancellable *cancellable)
++{
++  Request *request = (Request *)task_data;
++  Session *session;
++  WebExtensionsSession *we_session;
++  const char *arg_name;
++  const char *arg_extension_or_origin;
++  const char *app_id;
++  g_autofree char *server_path = NULL;
++  g_autofree char *server_description = NULL;
++  guint response = XDG_DESKTOP_PORTAL_RESPONSE_OTHER;
++  gboolean should_close_session;
++  Permission permission;
++  gboolean allowed;
++  char *argv[] = {NULL, NULL};
++  g_autoptr(GError) error = NULL;
++
++  REQUEST_AUTOLOCK (request);
++  session = g_object_get_data (G_OBJECT (request), "session");
++  SESSION_AUTOLOCK_UNREF (g_object_ref (session));
++  g_object_set_data (G_OBJECT (request), "session", NULL);
++  we_session = (WebExtensionsSession *)session;
++
++  if (!request->exported || we_session->state != WEB_EXTENSIONS_SESSION_STATE_STARTING)
++    goto out;
++
++  arg_name = g_object_get_data (G_OBJECT (request), "name");
++  arg_extension_or_origin = g_object_get_data (G_OBJECT (request), "extension-or-origin");
++
++  server_path = find_server (arg_name, arg_extension_or_origin, &server_description, &error);
++  if (server_path == NULL)
++    {
++      g_warning ("Could not find WebExtensions backend: %s", error->message);
++      goto out;
++    }
++
++  app_id = xdp_app_info_get_id (request->app_info);
++  permission = get_permission_sync (app_id, PERMISSION_TABLE, arg_name);
++  if (permission == PERMISSION_ASK || permission == PERMISSION_UNSET)
++    {
++      guint access_response = 2;
++      g_autoptr(GVariant) access_results = NULL;
++      GVariantBuilder opt_builder;
++      g_autoptr(GAppInfo) info = NULL;
++      g_auto(GStrv) app_id_components = NULL;
++      const char *display_name;
++      g_autofree gchar *title = NULL;
++      g_autofree gchar *subtitle = NULL;
++      g_autofree gchar *body = NULL;
++
++      info = xdp_app_info_load_app_info (request->app_info);
++      app_id_components = g_strsplit (g_app_info_get_id (info), ".desktop", 2);
++      display_name = info ? g_app_info_get_display_name (info) : app_id;
++      title = g_strdup_printf (_("Allow %s to start WebExtension backend?"), display_name);
++      subtitle = g_strdup_printf (_("%s is requesting to launch \"%s\" (%s)."), display_name, server_description, arg_name);
++      /* This UI does not currently exist */
++      body = g_strdup (_("This permission can be changed at any time from the privacy settings."));
++
++      g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
++      g_variant_builder_add (&opt_builder, "{sv}", "deny_label", g_variant_new_string (_("Don't allow")));
++      g_variant_builder_add (&opt_builder, "{sv}", "grant_label", g_variant_new_string (_("Allow")));
++      if (!xdp_dbus_impl_access_call_access_dialog_sync (access_impl,
++                                                         request->id,
++                                                         app_id_components[0],
++                                                         "",
++                                                         title,
++                                                         subtitle,
++                                                         body,
++                                                         g_variant_builder_end (&opt_builder),
++                                                         &access_response,
++                                                         &access_results,
++                                                         NULL,
++                                                         &error))
++        {
++          g_warning ("AccessDialog call failed: %s", error->message);
++          g_clear_error (&error);
++        }
++      allowed = access_response == 0;
++
++      if (permission == PERMISSION_UNSET)
++        set_permission_sync (app_id, PERMISSION_TABLE, arg_name, allowed ? PERMISSION_YES : PERMISSION_NO);
++    }
++  else
++    allowed = permission == PERMISSION_YES ? TRUE : FALSE;
++
++  if (!allowed)
++    {
++      response = XDG_DESKTOP_PORTAL_RESPONSE_CANCELLED;
++      goto out;
++    }
++
++  argv[0] = server_path;
++  if (!g_spawn_async_with_pipes (NULL, /* working_directory */
++                                 argv,
++                                 NULL, /* envp */
++                                 G_SPAWN_DO_NOT_REAP_CHILD,
++                                 NULL, /* child_setup */
++                                 NULL, /* user_data */
++                                 &we_session->child_pid,
++                                 &we_session->standard_input,
++                                 &we_session->standard_output,
++                                 &we_session->standard_error,
++                                 &error))
++    {
++      we_session->child_pid = -1;
++      goto out;
++    }
++
++  we_session->child_watch_id = g_child_watch_add_full (
++    G_PRIORITY_DEFAULT, we_session->child_pid, on_server_exited,
++    g_object_ref (we_session), g_object_unref);
++  we_session->state = WEB_EXTENSIONS_SESSION_STATE_STARTED;
++
++  response = XDG_DESKTOP_PORTAL_RESPONSE_SUCCESS;
++
++out:
++  should_close_session = !request->exported || response != XDG_DESKTOP_PORTAL_RESPONSE_SUCCESS;
++
++  if (request->exported)
++    {
++      GVariantBuilder results;
++
++      g_variant_builder_init (&results, G_VARIANT_TYPE_VARDICT);
++      xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, g_variant_builder_end (&results));
++      request_unexport (request);
++    }
++
++  if (should_close_session)
++    session_close (session, TRUE);
++}
++
++static gboolean
++handle_start (XdpDbusWebExtensions *object,
++              GDBusMethodInvocation *invocation,
++              const char *arg_session_handle,
++              const char *arg_name,
++              const char *arg_extension_or_origin,
++              GVariant *arg_options)
++{
++  Request *request = request_from_invocation (invocation);
++  Session *session;
++  WebExtensionsSession *we_session;
++  g_autoptr(GTask) task = NULL;
++
++  REQUEST_AUTOLOCK (request);
++
++  session = acquire_session (arg_session_handle, request);
++  if (!session)
++    {
++      g_dbus_method_invocation_return_error (invocation,
++                                             G_DBUS_ERROR,
++                                             G_DBUS_ERROR_ACCESS_DENIED,
++                                             "Invalid session");
++      return TRUE;
++    }
++
++  SESSION_AUTOLOCK_UNREF (session);
++  we_session = (WebExtensionsSession *)session;
++
++  if (we_session->state != WEB_EXTENSIONS_SESSION_STATE_INIT)
++    {
++      g_dbus_method_invocation_return_error (invocation,
++                                             G_DBUS_ERROR,
++                                             G_DBUS_ERROR_FAILED,
++                                             "Session already started");
++      return TRUE;
++    }
++
++  we_session->state = WEB_EXTENSIONS_SESSION_STATE_STARTING;
++  g_object_set_data_full (G_OBJECT (request), "session", g_object_ref (session), g_object_unref);
++  g_object_set_data_full (G_OBJECT (request), "name", g_strdup (arg_name), g_free);
++  g_object_set_data_full (G_OBJECT (request), "extension-or-origin", g_strdup (arg_extension_or_origin), g_free);
++
++  request_export (request, g_dbus_method_invocation_get_connection (invocation));
++  xdp_dbus_web_extensions_complete_start (object, invocation, request->id);
++
++  task = g_task_new (object, NULL, NULL, NULL);
++  g_task_set_task_data (task, g_object_ref (request), g_object_unref);
++  g_task_run_in_thread (task, handle_start_in_thread);
++
++  return TRUE;
++}
++
++
++static gboolean
++handle_get_pipes (XdpDbusWebExtensions *object,
++                  GDBusMethodInvocation *invocation,
++                  GUnixFDList *fd_list,
++                  const char *arg_session_handle,
++                  GVariant *arg_options)
++{
++  Call *call = call_from_invocation (invocation);
++  Session *session;
++  WebExtensionsSession *we_session;
++  g_autoptr(GUnixFDList) out_fd_list = NULL;
++
++  session = acquire_session_from_call (arg_session_handle, call);
++  if (!session)
++    {
++      g_dbus_method_invocation_return_error (invocation,
++                                             G_DBUS_ERROR,
++                                             G_DBUS_ERROR_ACCESS_DENIED,
++                                             "Invalid session");
++      return TRUE;
++    }
++
++  SESSION_AUTOLOCK_UNREF (session);
++  we_session = (WebExtensionsSession *)session;
++
++  if (we_session->state != WEB_EXTENSIONS_SESSION_STATE_STARTED)
++    {
++      g_dbus_method_invocation_return_error (invocation,
++                                             G_DBUS_ERROR,
++                                             G_DBUS_ERROR_FAILED,
++                                             "Session not started");
++      return TRUE;
++    }
++
++  if (we_session->standard_input < 0 ||
++      we_session->standard_output < 0 ||
++      we_session->standard_error < 0)
++    {
++      g_dbus_method_invocation_return_error (invocation,
++                                             G_DBUS_ERROR,
++                                             G_DBUS_ERROR_FAILED,
++                                             "GetPipes already called");
++      return TRUE;
++    }
++
++  int fds[3] = {
++    we_session->standard_input,
++    we_session->standard_output,
++    we_session->standard_error,
++  };
++  out_fd_list = g_unix_fd_list_new_from_array (fds, G_N_ELEMENTS (fds));
++  /* out_fd_list now owns the file descriptors */
++  we_session->standard_input = -1;
++  we_session->standard_output = -1;
++  we_session->standard_error = -1;
++
++  xdp_dbus_web_extensions_complete_get_pipes (object, invocation, out_fd_list,
++                                              g_variant_new_handle (0),
++                                              g_variant_new_handle (1),
++                                              g_variant_new_handle (2));
++  return TRUE;
++}
++
++static void
++web_extensions_iface_init (XdpDbusWebExtensionsIface *iface)
++{
++  iface->handle_create_session = handle_create_session;
++  iface->handle_start = handle_start;
++  iface->handle_get_pipes = handle_get_pipes;
++}
++
++static void
++web_extensions_init (WebExtensions *web_extensions)
++{
++  xdp_dbus_web_extensions_set_version (XDP_DBUS_WEB_EXTENSIONS (web_extensions), 1);
++}
++
++static void
++web_extensions_class_init (WebExtensionsClass *klass)
++{
++}
++
++GDBusInterfaceSkeleton *
++web_extensions_create (GDBusConnection *connection,
++                       const char *dbus_name_access)
++{
++  g_autoptr(GError) error = NULL;
++
++  web_extensions = g_object_new (web_extensions_get_type (), NULL);
++
++  access_impl = xdp_dbus_impl_access_proxy_new_sync (connection,
++                                                     G_DBUS_PROXY_FLAGS_NONE,
++                                                     dbus_name_access,
++                                                     DESKTOP_PORTAL_OBJECT_PATH,
++                                                     NULL,
++                                                     &error);
++
++  return G_DBUS_INTERFACE_SKELETON (web_extensions);
++}
+diff --git a/src/webextensions.h b/src/webextensions.h
+new file mode 100644
+index 0000000..72b947a
+--- /dev/null
++++ b/src/webextensions.h
+@@ -0,0 +1,24 @@
++/*
++ * Copyright © 2022 Canonical Ltd
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
++ *
++ */
++
++#pragma once
++
++#include <gio/gio.h>
++
++GDBusInterfaceSkeleton *web_extensions_create (GDBusConnection *connection,
++                                               const char *access_dbus_name);
+diff --git a/src/xdg-desktop-portal.c b/src/xdg-desktop-portal.c
+index ff96cc5..1000954 100644
+--- a/src/xdg-desktop-portal.c
++++ b/src/xdg-desktop-portal.c
+@@ -60,6 +60,7 @@
+ #include "settings.h"
+ #include "trash.h"
+ #include "wallpaper.h"
++#include "webextensions.h"
+ 
+ static GMainLoop *loop = NULL;
+ 
+@@ -138,6 +139,13 @@ method_needs_request (GDBusMethodInvocation *invocation)
+       else
+         return TRUE;
+     }
++  else if (strcmp (interface, "org.freedesktop.portal.WebExtensions") == 0)
++    {
++      if (strcmp (method, "Start") == 0)
++        return TRUE;
++      else
++        return FALSE;
++    }
+   else
+     {
+       return TRUE;
+@@ -289,6 +297,8 @@ on_bus_acquired (GDBusConnection *connection,
+     {
+       export_portal_implementation (connection,
+                                     device_create (connection, implementation->dbus_name, lockdown));
++      export_portal_implementation (connection,
++                                    web_extensions_create (connection, implementation->dbus_name));
+ #ifdef HAVE_GEOCLUE
+       export_portal_implementation (connection,
+                                     location_create (connection, implementation->dbus_name, lockdown));
+diff --git a/src/xdp-utils.c b/src/xdp-utils.c
+index b452534..0c6ba80 100644
+--- a/src/xdp-utils.c
++++ b/src/xdp-utils.c
+@@ -558,6 +558,29 @@ xdp_app_info_has_network (XdpAppInfo *app_info)
+   return has_network;
+ }
+ 
++/* Is the app allowed to start WebExtensions native messaging servers? */
++gboolean
++xdp_app_info_is_web_browser (XdpAppInfo  *app_info)
++{
++  gboolean is_web_browser;
++
++  /* TODO: this should include Flatpak and Snap specific checks to
++   * limit access to the webextensions portal to "web browsers".
++   *
++   * For snaps, this might be packages plugging "browser-support", or
++   * possibly something more targeted.
++   */
++  switch (app_info->kind)
++    {
++    case XDP_APP_INFO_KIND_HOST:
++    default:
++      is_web_browser = TRUE;
++      break;
++    }
++  return is_web_browser;
++}
++
++
+ static void
+ ensure_app_info_by_unique_name (void)
+ {
+diff --git a/src/xdp-utils.h b/src/xdp-utils.h
+index f5c5fda..36d915e 100644
+--- a/src/xdp-utils.h
++++ b/src/xdp-utils.h
+@@ -102,6 +102,7 @@ char *      xdp_app_info_get_path_for_fd (XdpAppInfo  *app_info,
+                                           gboolean    *writable_out,
+                                           GError     **error);
+ gboolean    xdp_app_info_has_network     (XdpAppInfo  *app_info);
++gboolean    xdp_app_info_is_web_browser  (XdpAppInfo  *app_info);
+ XdpAppInfo *xdp_get_app_info_from_pid    (pid_t        pid,
+                                           GError     **error);
+ GAppInfo *  xdp_app_info_load_app_info   (XdpAppInfo *app_info);
