This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch wl/browser-all
in repository enlightenment.

View the commit online.

commit a560ed1600a356e736d6ce274c241087056823dd
Author: Cedric BAIL <[email protected]>
AuthorDate: Tue Aug 18 12:47:44 2026 -0600

    e_comp_wl - speak xdg-foreign v2 as well as v1
    
    Same protocol twice: v2 renames export to export_toplevel, import to
    import_toplevel, and renames the four interfaces. The model, the handle,
    the lifetimes and the parent relationship are identical, so both run
    through one implementation and each object records which spelling created
    it - needed only where an event goes out, because
    zxdg_imported_v1_send_destroyed and its v2 twin write to different
    interfaces.
    
    Chromium binds v2 where it is offered and falls back to v1 otherwise, so
    until now it was always taking the fallback.
    
    One handle namespace covers both, deliberately. A handle is an opaque
    string and nothing in either version says it may only be imported by a
    client speaking the same one - a portal that exports with v2 while a
    dialog imports with v1 is an ordinary arrangement, and two namespaces
    would break it for no gain.
    
    That sharing is what the test is really for. Each version working on its
    own is the easy half and is what a side-by-side implementation with
    separate hashes would also pass; the crossings are what it would fail. So
    all four pairings run, crossings first, and each asserts the thing the
    protocol exists for - E reporting the imported window's parent as the
    exported one, read back over the wl_test back door because ec->parent is
    not visible to any client.
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
    Claude-Session: https://claude.ai/code/session_01FtoiXoSKUmZb6Aix6U3GZS
---
 src/bin/e_comp_wl.h                  |  11 ++
 src/bin/e_comp_wl_extensions.c       | 128 ++++++++++++++--
 src/bin/generated/meson.build        |   1 +
 src/tests/wayland/globals.expected   |   2 +
 src/tests/wayland/meson.build        |   1 +
 src/tests/wayland/test_xdg_foreign.c | 277 ++++++++++++++++++++++++++++++-----
 6 files changed, 367 insertions(+), 53 deletions(-)

diff --git a/src/bin/e_comp_wl.h b/src/bin/e_comp_wl.h
index 0a453a69e..36bde7b59 100644
--- a/src/bin/e_comp_wl.h
+++ b/src/bin/e_comp_wl.h
@@ -184,6 +184,17 @@ typedef struct E_Comp_Wl_Extension_Data
      {
         struct wl_global *global;
      } zxdg_importer_v1;
+   /* v2 is v1 with two requests renamed. It carries no state of its own -
+    * the handle namespace is zxdg_exporter_v1.surfaces above, shared so a
+    * handle exported by either version can be imported by either. */
+   struct
+     {
+        struct wl_global *global;
+     } zxdg_exporter_v2;
+   struct
+     {
+        struct wl_global *global;
+     } zxdg_importer_v2;
    struct
      {
         struct wl_global *global;
diff --git a/src/bin/e_comp_wl_extensions.c b/src/bin/e_comp_wl_extensions.c
index 94b975e68..8e800e72a 100644
--- a/src/bin/e_comp_wl_extensions.c
+++ b/src/bin/e_comp_wl_extensions.c
@@ -4,6 +4,7 @@
 #include <uuid.h>
 #include "session-recovery-server-protocol.h"
 #include "xdg-foreign-unstable-v1-server-protocol.h"
+#include "xdg-foreign-unstable-v2-server-protocol.h"
 #include "relative-pointer-unstable-v1-server-protocol.h"
 #include "pointer-constraints-unstable-v1-server-protocol.h"
 #include "action_route-server-protocol.h"
@@ -21,12 +22,28 @@
 /* mutter uses 32, seems reasonable */
 #define HANDLE_LEN 32
 
+/* xdg-foreign v1 and v2 are the same protocol twice.
+ *
+ * v2 renames two requests - export -> export_toplevel, import ->
+ * import_toplevel - and renames the four interfaces. The model, the handle,
+ * the lifetimes and the parent relationship are identical, so both versions
+ * run through one implementation and each object records which spelling it
+ * was created with. That is only needed where an *event* goes out, because
+ * zxdg_imported_v1_send_destroyed and its v2 twin are different functions
+ * writing to different interfaces.
+ *
+ * One handle namespace covers both, deliberately. A handle is an opaque
+ * string and nothing in either version says it may only be imported by a
+ * client speaking the same one - a portal that exports with v2 and a dialog
+ * that imports with v1 is a real arrangement, and two namespaces would break
+ * it for no gain. */
 typedef struct Exported
 {
    E_Client *ec;
    struct wl_resource *res;
    char handle[HANDLE_LEN + 1];
    Eina_List *imported;
+   int version;                 /* 1 or 2: which spelling created this */
 } Exported;
 
 typedef struct Imported
@@ -35,6 +52,7 @@ typedef struct Imported
    E_Client *ec;
    struct wl_resource *res;
    Exported *ex;
+   int version;                 /* 1 or 2: which spelling created this */
 } Imported;
 
 typedef struct Constraint
@@ -231,6 +249,15 @@ _e_comp_wl_zxdg_exported_v1_destroy(struct wl_client *client EINA_UNUSED, struct
 static void _imported_v1_del(Imported *im);
 static void _exported_del(void *data, Evas *e, Evas_Object *obj, void *event_info);
 
+/* The one place the two versions genuinely differ: the same event on two
+ * different interfaces. */
+static void
+_imported_send_destroyed(Imported *im)
+{
+   if (im->version >= 2) zxdg_imported_v2_send_destroyed(im->res);
+   else zxdg_imported_v1_send_destroyed(im->res);
+}
+
 static void
 _exported_v1_del(Exported *ex)
 {
@@ -238,7 +265,7 @@ _exported_v1_del(Exported *ex)
      {
         Imported *im = eina_list_data_get(ex->imported);
 
-        zxdg_imported_v1_send_destroyed(im->res);
+        _imported_send_destroyed(im);
         _imported_v1_del(im);
      }
    evas_object_event_callback_del(ex->ec->frame, EVAS_CALLBACK_DEL, _exported_del);
@@ -272,8 +299,13 @@ static const struct zxdg_exported_v1_interface _e_zxdg_exported_v1_interface =
    _e_comp_wl_zxdg_exported_v1_destroy,
 };
 
+static const struct zxdg_exported_v2_interface _e_zxdg_exported_v2_interface =
+{
+   _e_comp_wl_zxdg_exported_v1_destroy,
+};
+
 static void
-_e_comp_wl_zxdg_exporter_v1_export(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface)
+_e_comp_wl_zxdg_export(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface, int version)
 {
    E_Client *ec = wl_resource_get_user_data(surface);
    Exported *ex;
@@ -286,8 +318,17 @@ _e_comp_wl_zxdg_exporter_v1_export(struct wl_client *client, struct wl_resource
 
    ex = E_NEW(Exported, 1);
    ex->ec = ec;
-   ex->res = wl_resource_create(client, &zxdg_exported_v1_interface, wl_resource_get_version(resource), id);
-   wl_resource_set_implementation(ex->res, &_e_zxdg_exported_v1_interface, ex, _e_zxdg_exported_v1_del);
+   ex->version = version;
+   if (version >= 2)
+     {
+        ex->res = wl_resource_create(client, &zxdg_exported_v2_interface, wl_resource_get_version(resource), id);
+        wl_resource_set_implementation(ex->res, &_e_zxdg_exported_v2_interface, ex, _e_zxdg_exported_v1_del);
+     }
+   else
+     {
+        ex->res = wl_resource_create(client, &zxdg_exported_v1_interface, wl_resource_get_version(resource), id);
+        wl_resource_set_implementation(ex->res, &_e_zxdg_exported_v1_interface, ex, _e_zxdg_exported_v1_del);
+     }
    evas_object_event_callback_add(ec->frame, EVAS_CALLBACK_DEL, _exported_del, ex);
 
    do
@@ -299,10 +340,24 @@ _e_comp_wl_zxdg_exporter_v1_export(struct wl_client *client, struct wl_resource
              /* only printable ascii */
              ex->handle[n] = (rand() % (127 - 32)) + 32;
           }
+        /* One namespace for both versions - see the note on Exported. */
      } while (eina_hash_find(e_comp_wl->extensions->zxdg_exporter_v1.surfaces, ex->handle));
    eina_hash_add(e_comp_wl->extensions->zxdg_exporter_v1.surfaces, ex->handle, ex);
 
-   zxdg_exported_v1_send_handle(ex->res, ex->handle);
+   if (version >= 2) zxdg_exported_v2_send_handle(ex->res, ex->handle);
+   else zxdg_exported_v1_send_handle(ex->res, ex->handle);
+}
+
+static void
+_e_comp_wl_zxdg_exporter_v1_export(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface)
+{
+   _e_comp_wl_zxdg_export(client, resource, id, surface, 1);
+}
+
+static void
+_e_comp_wl_zxdg_exporter_v2_export_toplevel(struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface)
+{
+   _e_comp_wl_zxdg_export(client, resource, id, surface, 2);
 }
 
 
@@ -360,13 +415,13 @@ _e_comp_wl_zxdg_imported_v1_set_parent_of(struct wl_client *client EINA_UNUSED,
     * the resource itself lives on until the client gets round to destroying
     * it - and a client is entitled to call set_parent_of in that window,
     * because 'destroyed' is an event it may not have read yet and the request
-    * was already in flight before it could have been.
+    * was in flight before it could have.
     *
     * Every line below dereferences im, so this was a compositor-wide crash
-    * any client could cause deliberately with two requests and no privileges:
-    * import a handle nobody exported, then set_parent_of on it. Nothing to do
-    * but ignore the request - the object is already dead and the client has
-    * been told so. */
+    * that any client could cause deliberately with two requests and no
+    * privileges: import a handle nobody exported, then set_parent_of on it.
+    * Nothing to do but ignore the request - the object is already dead and
+    * the client has been told so. */
    if (!im) return;
 
    if (surface_resource) ec = wl_resource_get_user_data(surface_resource);
@@ -398,20 +453,36 @@ static const struct zxdg_imported_v1_interface _e_zxdg_imported_v1_interface =
    _e_comp_wl_zxdg_imported_v1_set_parent_of,
 };
 
+static const struct zxdg_imported_v2_interface _e_zxdg_imported_v2_interface =
+{
+   _e_comp_wl_zxdg_imported_v1_destroy,
+   _e_comp_wl_zxdg_imported_v1_set_parent_of,
+};
+
 static void
-_e_comp_wl_zxdg_importer_v1_import(struct wl_client *client, struct wl_resource *resource, uint32_t id, const char *handle)
+_e_comp_wl_zxdg_import(struct wl_client *client, struct wl_resource *resource, uint32_t id, const char *handle, int version)
 {
    Imported *im;
    Exported *ex;
 
    im = E_NEW(Imported, 1);
-   im->res = wl_resource_create(client, &zxdg_imported_v1_interface, wl_resource_get_version(resource), id);
-   wl_resource_set_implementation(im->res, &_e_zxdg_imported_v1_interface, NULL, _e_zxdg_imported_v1_del);
+   im->version = version;
+   if (version >= 2)
+     {
+        im->res = wl_resource_create(client, &zxdg_imported_v2_interface, wl_resource_get_version(resource), id);
+        wl_resource_set_implementation(im->res, &_e_zxdg_imported_v2_interface, NULL, _e_zxdg_imported_v1_del);
+     }
+   else
+     {
+        im->res = wl_resource_create(client, &zxdg_imported_v1_interface, wl_resource_get_version(resource), id);
+        wl_resource_set_implementation(im->res, &_e_zxdg_imported_v1_interface, NULL, _e_zxdg_imported_v1_del);
+     }
 
+   /* A handle exported by either version resolves here: one namespace. */
    ex = eina_hash_find(e_comp_wl->extensions->zxdg_exporter_v1.surfaces, handle);
    if ((!ex) || (!ex->ec->netwm.type))
      {
-        zxdg_imported_v1_send_destroyed(im->res);
+        _imported_send_destroyed(im);
         free(im);
         return;
      }
@@ -421,6 +492,18 @@ _e_comp_wl_zxdg_importer_v1_import(struct wl_client *client, struct wl_resource
    ex->imported = eina_list_append(ex->imported, im);
 }
 
+static void
+_e_comp_wl_zxdg_importer_v1_import(struct wl_client *client, struct wl_resource *resource, uint32_t id, const char *handle)
+{
+   _e_comp_wl_zxdg_import(client, resource, id, handle, 1);
+}
+
+static void
+_e_comp_wl_zxdg_importer_v2_import_toplevel(struct wl_client *client, struct wl_resource *resource, uint32_t id, const char *handle)
+{
+   _e_comp_wl_zxdg_import(client, resource, id, handle, 2);
+}
+
 /////////////////////////////////////////////////////////
 
 static void
@@ -1013,6 +1096,18 @@ static const struct zxdg_importer_v1_interface _e_zxdg_importer_v1_interface =
    _e_comp_wl_zxdg_importer_v1_import,
 };
 
+static const struct zxdg_exporter_v2_interface _e_zxdg_exporter_v2_interface =
+{
+   _e_comp_wl_zxdg_exporter_v1_exporter_destroy,
+   _e_comp_wl_zxdg_exporter_v2_export_toplevel,
+};
+
+static const struct zxdg_importer_v2_interface _e_zxdg_importer_v2_interface =
+{
+   _e_comp_wl_zxdg_importer_v1_importer_destroy,
+   _e_comp_wl_zxdg_importer_v2_import_toplevel,
+};
+
 static const struct zwp_relative_pointer_manager_v1_interface _e_zwp_relative_pointer_manager_v1_interface =
 {
    _e_comp_wl_zwp_relative_pointer_manager_v1_destroy,
@@ -2075,6 +2170,8 @@ GLOBAL_BIND_CB(session_recovery, zwp_e_session_recovery_interface)
 GLOBAL_BIND_CB(wp_viewporter, wp_viewporter_interface)
 GLOBAL_BIND_CB(zxdg_exporter_v1, zxdg_exporter_v1_interface)
 GLOBAL_BIND_CB(zxdg_importer_v1, zxdg_importer_v1_interface)
+GLOBAL_BIND_CB(zxdg_exporter_v2, zxdg_exporter_v2_interface)
+GLOBAL_BIND_CB(zxdg_importer_v2, zxdg_importer_v2_interface)
 GLOBAL_BIND_CB(zwp_relative_pointer_manager_v1, zwp_relative_pointer_manager_v1_interface)
 GLOBAL_BIND_CB(zwp_pointer_constraints_v1, zwp_pointer_constraints_v1_interface)
 GLOBAL_BIND_CB(xdg_activation_v1, xdg_activation_v1_interface)
@@ -2184,6 +2281,9 @@ e_comp_wl_extensions_init(void)
    GLOBAL_CREATE_OR_RETURN(zxdg_exporter_v1, zxdg_exporter_v1_interface, 1);
    e_comp_wl->extensions->zxdg_exporter_v1.surfaces = eina_hash_string_superfast_new(NULL);
    GLOBAL_CREATE_OR_RETURN(zxdg_importer_v1, zxdg_importer_v1_interface, 1);
+   /* v2 shares v1's handle namespace, so no second hash. */
+   GLOBAL_CREATE_OR_RETURN(zxdg_exporter_v2, zxdg_exporter_v2_interface, 1);
+   GLOBAL_CREATE_OR_RETURN(zxdg_importer_v2, zxdg_importer_v2_interface, 1);
    GLOBAL_CREATE_OR_RETURN(zwp_relative_pointer_manager_v1, zwp_relative_pointer_manager_v1_interface, 1);
    GLOBAL_CREATE_OR_RETURN(zwp_pointer_constraints_v1, zwp_pointer_constraints_v1_interface, 1);
    e_comp_wl->extensions->zwp_pointer_constraints_v1.constraints = eina_hash_pointer_new(NULL);
diff --git a/src/bin/generated/meson.build b/src/bin/generated/meson.build
index 49af589c1..99316e55d 100644
--- a/src/bin/generated/meson.build
+++ b/src/bin/generated/meson.build
@@ -4,6 +4,7 @@ protos = [
   '../../protocol/efl-aux-hints.xml',
   '../../protocol/action_route.xml',
   '@0@/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml'.format(dir_wayland_protocols),
+  '@0@/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml'.format(dir_wayland_protocols),
   '@0@/unstable/relative-pointer/relative-pointer-unstable-v1.xml'.format(dir_wayland_protocols),
   '@0@/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'.format(dir_wayland_protocols),
   '@0@/staging/xdg-activation/xdg-activation-v1.xml'.format(dir_wayland_protocols),
diff --git a/src/tests/wayland/globals.expected b/src/tests/wayland/globals.expected
index 67008e907..c74012ab5 100644
--- a/src/tests/wayland/globals.expected
+++ b/src/tests/wayland/globals.expected
@@ -21,6 +21,8 @@ zwp_primary_selection_device_manager_v1	1
 zwp_relative_pointer_manager_v1	1
 zxdg_decoration_manager_v1	1
 zxdg_exporter_v1	1
+zxdg_exporter_v2	1
 zxdg_importer_v1	1
+zxdg_importer_v2	1
 zxdg_output_manager_v1	3
 zxdg_shell_v6	1
diff --git a/src/tests/wayland/meson.build b/src/tests/wayland/meson.build
index 86d2fc09c..0fdb4ba4a 100644
--- a/src/tests/wayland/meson.build
+++ b/src/tests/wayland/meson.build
@@ -42,6 +42,7 @@ foreach p: [
   '@0@/unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml'.format(dir_wayland_protocols),
   '@0@/stable/presentation-time/presentation-time.xml'.format(dir_wayland_protocols),
   '@0@/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml'.format(dir_wayland_protocols),
+  '@0@/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml'.format(dir_wayland_protocols),
 ]
   test_proto_src += gen_scanner_client.process(p)
   test_proto_src += gen_scanner_impl.process(p)
diff --git a/src/tests/wayland/test_xdg_foreign.c b/src/tests/wayland/test_xdg_foreign.c
index db74b353b..066e3f213 100644
--- a/src/tests/wayland/test_xdg_foreign.c
+++ b/src/tests/wayland/test_xdg_foreign.c
@@ -1,22 +1,22 @@
-/* xdg-foreign: what happens after an import that could not succeed.
+/* xdg-foreign: a window in one process made the child of a window in another.
  *
- * zxdg_importer_v1.import takes an opaque handle string. When no window
- * matches it the compositor answers 'destroyed' and that is the end of the
- * object - but 'destroyed' is an *event*, and a client that sent import and
- * set_parent_of back to back could not possibly have read it in between. The
- * second request arrives at a compositor that has already thrown away its
- * bookkeeping for the object.
+ * E has shipped v1 for years and now also speaks v2, which is the same
+ * protocol with two requests renamed - export -> export_toplevel, import ->
+ * import_toplevel. Chromium binds v2 where it is offered and falls back to v1
+ * otherwise, so before this it was always taking the fallback.
  *
- * E freed the Imported on the failure and left the wl_resource alive with a
- * NULL behind it, so the set_parent_of that followed dereferenced NULL. Two
- * requests, no privileges, any client on the socket, and the whole compositor
- * goes down with every window on it. Verified before the fix: SIGSEGV, core
- * dumped.
+ * The interesting question is not whether each version works on its own. It is
+ * whether they share a handle namespace: a handle is an opaque string, nothing
+ * in either version says it may only be imported by a client speaking the same
+ * one, and a portal exporting with v2 while a dialog imports with v1 is an
+ * ordinary arrangement. Two implementations sitting side by side with separate
+ * hashes would pass every single-version test and fail that.
  *
- * The assertion is only that we are still connected afterwards, which looks
- * like asserting nothing - and that is what a crash test looks like when it
- * passes. Checked against a build with the guard removed to be sure it can
- * still fail.
+ * So both crossings are exercised, in both directions, and the same-version
+ * cases come along for free. What is asserted is the thing the protocol is
+ * for: E reporting the imported window's parent as the exported one, which it
+ * does over the wl_test back door - `ec->parent` is not otherwise visible to
+ * any client.
  */
 #include <stdio.h>
 #include <stdlib.h>
@@ -24,10 +24,45 @@
 
 #include "e_wl_testkit.h"
 #include "xdg-foreign-unstable-v1-client-protocol.h"
+#include "xdg-foreign-unstable-v2-client-protocol.h"
 
 #define PROG "test-xdg-foreign"
 
-static int destroyed_v1;
+static Tk *tk;
+static char handle[256];
+static int have_handle;
+
+static void
+_exported_v1_handle(void *data, struct zxdg_exported_v1 *e, const char *h)
+{
+   (void)data; (void)e;
+   snprintf(handle, sizeof(handle), "%s", h);
+   have_handle++;
+}
+
+static const struct zxdg_exported_v1_listener _exported_v1_listener =
+{
+   _exported_v1_handle
+};
+
+static void
+_exported_v2_handle(void *data, struct zxdg_exported_v2 *e, const char *h)
+{
+   (void)data; (void)e;
+   snprintf(handle, sizeof(handle), "%s", h);
+   have_handle++;
+}
+
+static const struct zxdg_exported_v2_listener _exported_v2_listener =
+{
+   _exported_v2_handle
+};
+
+/* A compositor that cannot resolve the handle answers 'destroyed' straight
+ * away. That is the failure this test is really watching for on the crossed
+ * cases - it is what a second, private handle namespace looks like from the
+ * client side, and it is not an error, so nothing else would report it. */
+static int destroyed_v1, destroyed_v2;
 
 static void
 _imported_v1_destroyed(void *data, struct zxdg_imported_v1 *i)
@@ -41,43 +76,207 @@ static const struct zxdg_imported_v1_listener _imported_v1_listener =
    _imported_v1_destroyed
 };
 
+static void
+_imported_v2_destroyed(void *data, struct zxdg_imported_v2 *i)
+{
+   (void)data; (void)i;
+   destroyed_v2++;
+}
+
+static const struct zxdg_imported_v2_listener _imported_v2_listener =
+{
+   _imported_v2_destroyed
+};
+
+static struct zxdg_exporter_v1 *exporter_v1;
+static struct zxdg_importer_v1 *importer_v1;
+static struct zxdg_exporter_v2 *exporter_v2;
+static struct zxdg_importer_v2 *importer_v2;
+
+/* Export `parent_app`, import the handle with the other version, and make
+ * `child_app` its child. Returns once E has been asked; the caller checks.
+ *
+ * export_version and import_version are deliberately separate arguments: the
+ * whole point is to run them mismatched. */
+static void
+_adopt(Tk_Toplevel *parent, Tk_Toplevel *child, int export_version, int import_version)
+{
+   have_handle = 0;
+   handle[0] = 0;
+
+   if (export_version >= 2)
+     {
+        struct zxdg_exported_v2 *ex;
+
+        ex = zxdg_exporter_v2_export_toplevel(exporter_v2,
+                                              tk_toplevel_surface(parent));
+        zxdg_exported_v2_add_listener(ex, &_exported_v2_listener, NULL);
+     }
+   else
+     {
+        struct zxdg_exported_v1 *ex;
+
+        ex = zxdg_exporter_v1_export(exporter_v1, tk_toplevel_surface(parent));
+        zxdg_exported_v1_add_listener(ex, &_exported_v1_listener, NULL);
+     }
+
+   tk_sync(tk);
+   if (!have_handle)
+     tk_fail(tk, "exported with v%d and got no handle back", export_version);
+
+   if (import_version >= 2)
+     {
+        struct zxdg_imported_v2 *im;
+
+        im = zxdg_importer_v2_import_toplevel(importer_v2, handle);
+        zxdg_imported_v2_add_listener(im, &_imported_v2_listener, NULL);
+        zxdg_imported_v2_set_parent_of(im, tk_toplevel_surface(child));
+     }
+   else
+     {
+        struct zxdg_imported_v1 *im;
+
+        im = zxdg_importer_v1_import(importer_v1, handle);
+        zxdg_imported_v1_add_listener(im, &_imported_v1_listener, NULL);
+        zxdg_imported_v1_set_parent_of(im, tk_toplevel_surface(child));
+     }
+
+   tk_settle(tk);
+}
+
+static void
+_check(const char *child_app, unsigned int want_parent, int export_version, int import_version, int destroyed_before)
+{
+   Tk_Client *c;
+   int destroyed_now = (import_version >= 2) ? destroyed_v2 : destroyed_v1;
+
+   if (destroyed_now != destroyed_before)
+     tk_fail(tk, "exported with v%d, imported with v%d, and the compositor "
+                 "answered 'destroyed' - it did not recognise a handle it had "
+                 "just issued. The two versions are not sharing a handle "
+                 "namespace, which breaks every mixed-version pairing while "
+                 "each version alone looks perfect",
+             export_version, import_version);
+
+   c = tk_expect(tk, child_app);
+   if (c->parent != want_parent)
+     tk_fail(tk, "exported with v%d, imported with v%d: '%s' reports parent "
+                 "%u, expected %u. The handle resolved but set_parent_of did "
+                 "not take effect",
+             export_version, import_version, child_app, c->parent, want_parent);
+
+   printf(PROG ": export v%d -> import v%d: parent set\n",
+          export_version, import_version);
+}
+
 int
 main(void)
 {
-   Tk *tk;
-   Tk_Toplevel *kid;
-   struct zxdg_importer_v1 *importer_v1;
-   struct zxdg_imported_v1 *bad;
+   Tk_Toplevel *parent, *kid;
+   Tk_Client *c;
+   unsigned int parent_id;
+   uint32_t v;
 
    tk = tk_connect(PROG);
 
-   if (tk_global_version(tk, "zxdg_importer_v1") < 1)
-     tk_fail(tk, "no zxdg_importer_v1");
+   v = tk_global_version(tk, "zxdg_exporter_v2");
+   if (v < 1)
+     tk_fail(tk, "no zxdg_exporter_v2. E has shipped v1 for years, and "
+                 "Chromium binds v2 when it is there and silently falls back "
+                 "to v1 when it is not - so the gap is invisible until "
+                 "something only speaks v2");
+   if (tk_global_version(tk, "zxdg_importer_v2") < 1)
+     tk_fail(tk, "zxdg_exporter_v2 without zxdg_importer_v2 - a handle nobody "
+                 "can import is not useful");
+   if (tk_global_version(tk, "zxdg_exporter_v1") < 1)
+     tk_fail(tk, "zxdg_exporter_v1 has gone missing. v2 is an addition, not a "
+                 "replacement: clients that only speak v1 still exist");
 
+   exporter_v1 = tk_bind(tk, &zxdg_exporter_v1_interface, 1);
    importer_v1 = tk_bind(tk, &zxdg_importer_v1_interface, 1);
-   if (!importer_v1) tk_fail(tk, "advertised but would not bind");
+   exporter_v2 = tk_bind(tk, &zxdg_exporter_v2_interface, 1);
+   importer_v2 = tk_bind(tk, &zxdg_importer_v2_interface, 1);
+   if ((!exporter_v1) || (!importer_v1) || (!exporter_v2) || (!importer_v2))
+     tk_fail(tk, "advertised but would not bind");
 
+   parent = tk_toplevel_new(tk, "foreign-parent", "parent", 300, 200);
    kid = tk_toplevel_new(tk, "foreign-child", "child", 200, 150);
    tk_settle(tk);
-   tk_expect(tk, "foreign-child");
 
-   /* Back to back, deliberately: the whole point is that the client has not
-    * read 'destroyed' yet when it sends the second request. */
-   bad = zxdg_importer_v1_import(importer_v1, "no-such-handle");
-   zxdg_imported_v1_add_listener(bad, &_imported_v1_listener, NULL);
-   zxdg_imported_v1_set_parent_of(bad, tk_toplevel_surface(kid));
+   c = tk_expect(tk, "foreign-parent");
+   parent_id = c->id;
 
-   /* tk_sync exits the process with "disconnected during sync" if the
-    * compositor has gone, so reaching the line after it is the assertion. */
-   tk_sync(tk);
-   tk_expect(tk, "foreign-child");
+   c = tk_expect(tk, "foreign-child");
+   if (c->parent != 0)
+     tk_fail(tk, "the child already has parent %u before anything was "
+                 "imported", c->parent);
 
-   if (!destroyed_v1)
-     tk_fail(tk, "importing a handle nobody exported was not answered with "
-                 "'destroyed' - the client is left holding an object that "
-                 "will never resolve");
+   /* The crossings first, because they are the ones that can fail while
+    * everything else passes. */
+   {
+      int before = destroyed_v1;
+
+      _adopt(parent, kid, 2, 1);
+      _check("foreign-child", parent_id, 2, 1, before);
+   }
+
+   {
+      int before = destroyed_v2;
+
+      _adopt(parent, kid, 1, 2);
+      _check("foreign-child", parent_id, 1, 2, before);
+   }
+
+   /* And each version on its own, which is what everyone else tests and what
+    * must not have regressed while v2 was added. */
+   {
+      int before = destroyed_v1;
+
+      _adopt(parent, kid, 1, 1);
+      _check("foreign-child", parent_id, 1, 1, before);
+   }
+
+   {
+      int before = destroyed_v2;
+
+      _adopt(parent, kid, 2, 2);
+      _check("foreign-child", parent_id, 2, 2, before);
+   }
+
+   /* -------------------------------- an import that cannot succeed */
+
+   /* A handle nobody exported, followed immediately by set_parent_of.
+    *
+    * Not a contrived sequence: the compositor answers an unknown handle with
+    * 'destroyed', but that is an event, and a client that sent both requests
+    * back to back could not have read it yet. E used to free its bookkeeping
+    * on the failure and leave the resource alive with a NULL behind it, so
+    * the set_parent_of that followed dereferenced NULL and took the whole
+    * compositor down - two requests, no privileges, any client.
+    *
+    * Asserting "we are still connected afterwards" looks like asserting
+    * nothing, which is exactly what a crash test looks like when it passes.
+    * Both versions, because the handler is shared and either spelling
+    * reaches it. */
+   {
+      struct zxdg_imported_v1 *bad1;
+      struct zxdg_imported_v2 *bad2;
+
+      bad1 = zxdg_importer_v1_import(importer_v1, "no-such-handle");
+      zxdg_imported_v1_add_listener(bad1, &_imported_v1_listener, NULL);
+      zxdg_imported_v1_set_parent_of(bad1, tk_toplevel_surface(kid));
+
+      bad2 = zxdg_importer_v2_import_toplevel(importer_v2, "no-such-handle");
+      zxdg_imported_v2_add_listener(bad2, &_imported_v2_listener, NULL);
+      zxdg_imported_v2_set_parent_of(bad2, tk_toplevel_surface(kid));
+
+      /* tk_sync exits the process with "disconnected during sync" if the
+       * compositor has gone, so reaching the next line is the assertion. */
+      tk_sync(tk);
+      tk_expect(tk, "foreign-child");
+      printf(PROG ": set_parent_of on a failed import - compositor survived\n");
+   }
 
-   printf(PROG ": set_parent_of after a failed import - compositor survived\n");
    printf(PROG ": ok\n");
    tk_disconnect(tk);
    return 0;

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to