From 7d0cb2f00685119218fafc3acedc4cf3118a7744 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Wed, 2 Oct 2024 14:51:33 -0300 Subject: [PATCH 1/2] atcontext: Factor out some code Move the code that realizes an AT context if the parent is realized, into to a separate function. This will make the next patch easier to read. No functional changes. --- gtk/gtkatcontext.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/gtk/gtkatcontext.c b/gtk/gtkatcontext.c index 78c76f0fa0..2aa137c80f 100644 --- a/gtk/gtkatcontext.c +++ b/gtk/gtkatcontext.c @@ -512,6 +512,17 @@ gtk_at_context_get_accessible_parent (GtkATContext *self) static GtkATContext * get_parent_context (GtkATContext *self); +static inline void +maybe_realize_context (GtkATContext *self) +{ + GtkATContext *parent_context = get_parent_context (self); + + if (parent_context && parent_context->realized) + gtk_at_context_realize (self); + + g_clear_object (&parent_context); +} + /*< private > * gtk_at_context_set_accessible_parent: * @self: a `GtkAtContext` @@ -534,15 +545,10 @@ gtk_at_context_set_accessible_parent (GtkATContext *self, self->accessible_parent = parent; if (self->accessible_parent != NULL) { - GtkATContext *parent_context = NULL; - g_object_add_weak_pointer (G_OBJECT (self->accessible_parent), (gpointer *) &self->accessible_parent); - parent_context = get_parent_context (self); - if (parent_context && parent_context->realized) - gtk_at_context_realize (self); - g_clear_object (&parent_context); + maybe_realize_context (self); } } } From f916d82280e787ffd6cde3095726576cc566a287 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Wed, 2 Oct 2024 14:55:14 -0300 Subject: [PATCH 2/2] atcontext: Always realize non-widget accessibles Upon joining the a11y tree. And do so recursively, as long as the parent is also not a widget. As for the explanation, please grab a mug of your favorite drink. It's a little complicated. GTK realizes AT contexts in 3 situations: 1. When it's a toplevel, it's realized unconditionally 2. When the widget is focused 3. When the accessible is appended to a realized parent Most importantly, GTK lazily realizes accessibles, and does not realize child accessibles recursively. Clearly, conditions 1 and 2 only ever happen for GtkWidgets, which are accessible objects themselves. These two conditions will handle the vast majority of cases of apps and platform libraries. However, there are non-widget accessibles out there. GTK itself offers a non-widget accessible implementation - GtkAtspiSocket - which is used by WebKitGTK. Now, let's look at WebKitGTK use case. It'll demonstrate the problem nicely. WebKitGTK creates the GtkAtspiSocket object *after* loading most of the page. At this point, there are 2 possibilities: 1. The web view widget is focused. In this case, the AT context of the web view is realized, and GTK will realize the GtkAtspiSocket when it is added to the a11y tree (condition 3 above). 2. The web view widget is *not* focused. At some point the user focuses the web view, and GTK will realize the AT context of the web view. But remember, GTK does not realize child accessibles! That means GtkAtspiSocket won't be realized. This example demonstrates a general problem with non-widget accessibles: non-widget accessibles cannot trigger conditions 1 and 2, so they're never realized. The only way they're realized in if they happen to be added to an already realized accessible (condition 3). To fix that, the following is proposed: always realize non-widget accessibles, and also of their non-widget accessible parents. This is not ideal, of course, as it might generate some D-Bus chattery, but GTK does not have enough information to realize these objects at more appropriate times. --- gtk/gtkatcontext.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/gtk/gtkatcontext.c b/gtk/gtkatcontext.c index 2aa137c80f..6e38f14cd5 100644 --- a/gtk/gtkatcontext.c +++ b/gtk/gtkatcontext.c @@ -515,12 +515,29 @@ static GtkATContext * get_parent_context (GtkATContext *self); static inline void maybe_realize_context (GtkATContext *self) { - GtkATContext *parent_context = get_parent_context (self); + GtkAccessible *accessible_parent; + GtkATContext *parent_context; - if (parent_context && parent_context->realized) + parent_context = get_parent_context (self); + + if (!GTK_IS_WIDGET (self->accessible) || (parent_context && parent_context->realized)) gtk_at_context_realize (self); g_clear_object (&parent_context); + + accessible_parent = self->accessible_parent; + while (accessible_parent && !GTK_IS_WIDGET (accessible_parent)) + { + parent_context = gtk_accessible_get_at_context (accessible_parent); + + if (!parent_context) + break; + + gtk_at_context_realize (parent_context); + accessible_parent = parent_context->accessible_parent; + + g_clear_object (&parent_context); + } } /*< private >