a11y: Reimplement name computation

Reimplement the name computation to follow the spec
(https://www.w3.org/TR/accname-1.2) more closely.

Also, unify the functions for name and description,
since their only difference is which property/relation
they use.
This commit is contained in:
Matthias Clasen
2023-06-18 18:56:14 -04:00
parent e1c9f50d26
commit 4449344fad

View File

@@ -1007,10 +1007,6 @@ gtk_at_context_get_accessible_relation (GtkATContext *self,
return gtk_accessible_attribute_set_get_value (self->relations, relation);
}
/* See the WAI-ARIA § 4.3, "Accessible Name and Description Computation",
* and https://www.w3.org/TR/accname-1.2/
*/
/* See ARIA 5.2.8.4, 5.2.8.5 and 5.2.8.6 for the prohibited, from author
* and from content parts, and the table in
* https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/
@@ -1147,172 +1143,6 @@ gtk_accessible_role_get_naming (GtkAccessibleRole role)
return (GtkAccessibleNaming) (naming[role] & ~(NAME_FROM_AUTHOR|NAME_FROM_CONTENT));
}
static void
gtk_at_context_get_name_accumulate (GtkATContext *self,
GPtrArray *names,
gboolean recurse)
{
GtkAccessibleValue *value = NULL;
if (gtk_accessible_attribute_set_contains (self->properties, GTK_ACCESSIBLE_PROPERTY_LABEL))
{
value = gtk_accessible_attribute_set_get_value (self->properties, GTK_ACCESSIBLE_PROPERTY_LABEL);
g_ptr_array_add (names, (char *) gtk_string_accessible_value_get (value));
}
if (recurse && gtk_accessible_attribute_set_contains (self->relations, GTK_ACCESSIBLE_RELATION_LABELLED_BY))
{
value = gtk_accessible_attribute_set_get_value (self->relations, GTK_ACCESSIBLE_RELATION_LABELLED_BY);
GList *list = gtk_reference_list_accessible_value_get (value);
for (GList *l = list; l != NULL; l = l->next)
{
GtkAccessible *rel = GTK_ACCESSIBLE (l->data);
GtkATContext *rel_context = gtk_accessible_get_at_context (rel);
gtk_at_context_get_name_accumulate (rel_context, names, FALSE);
g_object_unref (rel_context);
}
}
GtkAccessibleRole role = gtk_at_context_get_accessible_role (self);
switch ((int) role)
{
case GTK_ACCESSIBLE_ROLE_RANGE:
{
int range_attrs[] = {
GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT,
GTK_ACCESSIBLE_PROPERTY_VALUE_NOW,
};
value = NULL;
for (int i = 0; i < G_N_ELEMENTS (range_attrs); i++)
{
if (gtk_accessible_attribute_set_contains (self->properties, range_attrs[i]))
{
value = gtk_accessible_attribute_set_get_value (self->properties, range_attrs[i]);
break;
}
}
if (value != NULL)
g_ptr_array_add (names, (char *) gtk_string_accessible_value_get (value));
}
break;
default:
break;
}
/* If there is no label or labelled-by attribute, hidden elements
* have no name
*/
if (gtk_accessible_attribute_set_contains (self->states, GTK_ACCESSIBLE_STATE_HIDDEN))
{
value = gtk_accessible_attribute_set_get_value (self->states, GTK_ACCESSIBLE_STATE_HIDDEN);
if (gtk_boolean_accessible_value_get (value))
return;
}
if (names->len == 0)
{
if (GTK_IS_WIDGET (self->accessible))
{
const char *tooltip = gtk_widget_get_tooltip_text (GTK_WIDGET (self->accessible));
if (tooltip)
g_ptr_array_add (names, (char *) tooltip);
}
}
}
static void
gtk_at_context_get_description_accumulate (GtkATContext *self,
GPtrArray *labels,
gboolean recurse)
{
GtkAccessibleValue *value = NULL;
if (gtk_accessible_attribute_set_contains (self->properties, GTK_ACCESSIBLE_PROPERTY_DESCRIPTION))
{
value = gtk_accessible_attribute_set_get_value (self->properties, GTK_ACCESSIBLE_PROPERTY_DESCRIPTION);
g_ptr_array_add (labels, (char *) gtk_string_accessible_value_get (value));
}
if (recurse && gtk_accessible_attribute_set_contains (self->relations, GTK_ACCESSIBLE_RELATION_DESCRIBED_BY))
{
value = gtk_accessible_attribute_set_get_value (self->relations, GTK_ACCESSIBLE_RELATION_DESCRIBED_BY);
GList *list = gtk_reference_list_accessible_value_get (value);
for (GList *l = list; l != NULL; l = l->next)
{
GtkAccessible *rel = GTK_ACCESSIBLE (l->data);
GtkATContext *rel_context = gtk_accessible_get_at_context (rel);
gtk_at_context_get_description_accumulate (rel_context, labels, FALSE);
g_object_unref (rel_context);
}
}
GtkAccessibleRole role = gtk_at_context_get_accessible_role (self);
switch ((int) role)
{
case GTK_ACCESSIBLE_ROLE_RANGE:
{
int range_attrs[] = {
GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT,
GTK_ACCESSIBLE_PROPERTY_VALUE_NOW,
};
value = NULL;
for (int i = 0; i < G_N_ELEMENTS (range_attrs); i++)
{
if (gtk_accessible_attribute_set_contains (self->properties, range_attrs[i]))
{
value = gtk_accessible_attribute_set_get_value (self->properties, range_attrs[i]);
break;
}
}
if (value != NULL)
g_ptr_array_add (labels, (char *) gtk_string_accessible_value_get (value));
}
break;
default:
break;
}
/* If there is no description or described-by attribute, hidden elements
* have no description
*/
if (gtk_accessible_attribute_set_contains (self->states, GTK_ACCESSIBLE_STATE_HIDDEN))
{
value = gtk_accessible_attribute_set_get_value (self->states, GTK_ACCESSIBLE_STATE_HIDDEN);
if (gtk_boolean_accessible_value_get (value))
return;
}
if (labels->len == 0)
{
if (GTK_IS_WIDGET (self->accessible))
{
const char *tooltip = gtk_widget_get_tooltip_text (GTK_WIDGET (self->accessible));
if (tooltip)
g_ptr_array_add (labels, (char *) tooltip);
}
}
}
static gboolean
is_nested_button (GtkATContext *self)
{
@@ -1363,24 +1193,191 @@ get_parent_context (GtkATContext *self)
return g_object_ref (self);
}
static inline gboolean
not_just_space (const char *text)
{
for (const char *p = text; *p; p = g_utf8_next_char (p))
{
if (!g_unichar_isspace (g_utf8_get_char (p)))
return TRUE;
}
/*< private >
* gtk_at_context_get_name:
* @self: a `GtkATContext`
*
* Retrieves the accessible name of the `GtkATContext`.
*
* This is a convenience function meant to be used by `GtkATContext` implementations.
*
* Returns: (transfer full): the label of the `GtkATContext`
return FALSE;
}
static inline void
append_with_space (GString *str,
const char *text)
{
if (str->len > 0)
g_string_append (str, " ");
g_string_append (str, text);
}
/* See the WAI-ARIA § 4.3, "Accessible Name and Description Computation",
* and https://www.w3.org/TR/accname-1.2/
*/
char *
gtk_at_context_get_name (GtkATContext *self)
static void
gtk_at_context_get_text_accumulate (GtkATContext *self,
GPtrArray *nodes,
GString *res,
GtkAccessibleProperty property,
GtkAccessibleRelation relation,
gboolean is_ref,
gboolean is_child)
{
GtkAccessibleValue *value = NULL;
/* Step 2.A */
if (!is_ref)
{
if (gtk_accessible_attribute_set_contains (self->states, GTK_ACCESSIBLE_STATE_HIDDEN))
{
value = gtk_accessible_attribute_set_get_value (self->states, GTK_ACCESSIBLE_STATE_HIDDEN);
if (gtk_boolean_accessible_value_get (value))
return;
}
}
if (gtk_accessible_role_supports_name_from_author (self->accessible_role))
{
/* Step 2.B */
if (!is_ref && gtk_accessible_attribute_set_contains (self->relations, relation))
{
value = gtk_accessible_attribute_set_get_value (self->relations, relation);
GList *list = gtk_reference_list_accessible_value_get (value);
for (GList *l = list; l != NULL; l = l->next)
{
GtkAccessible *rel = GTK_ACCESSIBLE (l->data);
if (!g_ptr_array_find (nodes, rel, NULL))
{
GtkATContext *rel_context = gtk_accessible_get_at_context (rel);
g_ptr_array_add (nodes, rel);
gtk_at_context_get_text_accumulate (rel_context, nodes, res, property, relation, TRUE, FALSE);
g_object_unref (rel_context);
}
}
return;
}
/* Step 2.C */
if (gtk_accessible_attribute_set_contains (self->properties, property))
{
value = gtk_accessible_attribute_set_get_value (self->properties, property);
char *str = (char *) gtk_string_accessible_value_get (value);
if (str[0] != '\0')
{
append_with_space (res, gtk_string_accessible_value_get (value));
return;
}
}
}
/* Step 2.E */
switch ((int) self->accessible_role)
{
case GTK_ACCESSIBLE_ROLE_TEXT_BOX:
{
if (GTK_IS_EDITABLE (self->accessible))
{
const char *text = gtk_editable_get_text (GTK_EDITABLE (self->accessible));
if (text && not_just_space (text))
append_with_space (res, text);
}
}
return;
case GTK_ACCESSIBLE_ROLE_RANGE:
{
int range_attrs[] = {
GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT,
GTK_ACCESSIBLE_PROPERTY_VALUE_NOW,
};
value = NULL;
for (int i = 0; i < G_N_ELEMENTS (range_attrs); i++)
{
if (gtk_accessible_attribute_set_contains (self->properties, range_attrs[i]))
{
value = gtk_accessible_attribute_set_get_value (self->properties, range_attrs[i]);
break;
}
}
if (value != NULL)
append_with_space (res, gtk_string_accessible_value_get (value));
}
return;
default:
break;
}
/* Step 2.F */
if (gtk_accessible_role_supports_name_from_content (self->accessible_role) || is_ref || is_child)
{
if (GTK_IS_WIDGET (self->accessible))
{
gboolean has_child = FALSE;
for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self->accessible));
child != NULL;
child = gtk_widget_get_next_sibling (child))
{
GtkAccessible *rel = GTK_ACCESSIBLE (child);
GtkATContext *rel_context = gtk_accessible_get_at_context (rel);
gtk_at_context_get_text_accumulate (rel_context, nodes, res, property, relation, FALSE, TRUE);
has_child = TRUE;
}
if (has_child)
return;
}
}
/* Step 2.G */
if (GTK_IS_LABEL (self->accessible))
{
const char *text = gtk_label_get_text (GTK_LABEL (self->accessible));
if (text && not_just_space (text))
append_with_space (res, text);
return;
}
else if (GTK_IS_INSCRIPTION (self->accessible))
{
const char *text = gtk_inscription_get_text (GTK_INSCRIPTION (self->accessible));
if (text && not_just_space (text))
append_with_space (res, text);
return;
}
/* Step 2.I */
if (GTK_IS_WIDGET (self->accessible))
{
const char *text = gtk_widget_get_tooltip_text (GTK_WIDGET (self->accessible));
if (text && not_just_space (text))
append_with_space (res, text);
}
}
static char *
gtk_at_context_get_text (GtkATContext *self,
GtkAccessibleProperty property,
GtkAccessibleRelation relation)
{
GtkATContext *parent = NULL;
g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);
/* Step 1 */
if (gtk_accessible_role_get_naming (self->accessible_role) == GTK_ACCESSIBLE_NAME_PROHIBITED)
return g_strdup ("");
@@ -1402,33 +1399,37 @@ gtk_at_context_get_name (GtkATContext *self)
}
}
GPtrArray *names = g_ptr_array_new ();
gtk_at_context_get_name_accumulate (self, names, TRUE);
if (names->len == 0)
{
g_ptr_array_unref (names);
g_clear_object (&parent);
return g_strdup ("");
}
GPtrArray *nodes = g_ptr_array_new ();
GString *res = g_string_new ("");
g_string_append (res, g_ptr_array_index (names, 0));
for (guint i = 1; i < names->len; i++)
{
g_string_append (res, " ");
g_string_append (res, g_ptr_array_index (names, i));
}
/* Step 2 */
gtk_at_context_get_text_accumulate (self, nodes, res, property, relation, FALSE, FALSE);
g_ptr_array_unref (names);
g_ptr_array_unref (nodes);
g_clear_object (&parent);
return g_string_free (res, FALSE);
}
/*< private >
* gtk_at_context_get_name:
* @self: a `GtkATContext`
*
* Retrieves the accessible name of the `GtkATContext`.
*
* This is a convenience function meant to be used by `GtkATContext` implementations.
*
* Returns: (transfer full): the label of the `GtkATContext`
*/
char *
gtk_at_context_get_name (GtkATContext *self)
{
g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);
return gtk_at_context_get_text (self, GTK_ACCESSIBLE_PROPERTY_LABEL, GTK_ACCESSIBLE_RELATION_LABELLED_BY);
}
/*< private >
* gtk_at_context_get_description:
* @self: a `GtkATContext`
@@ -1442,49 +1443,9 @@ gtk_at_context_get_name (GtkATContext *self)
char *
gtk_at_context_get_description (GtkATContext *self)
{
GtkATContext *parent = NULL;
g_return_val_if_fail (GTK_IS_AT_CONTEXT (self), NULL);
if (gtk_accessible_role_get_naming (self->accessible_role) == GTK_ACCESSIBLE_NAME_PROHIBITED)
return g_strdup ("");
/* We special case this here since it is a common pattern:
* We have a 'wrapper' object, like a GtkDropdown which
* contains a toggle button. The dropdown appears in the
* ui file and carries all the a11y attributes, but the
* focus ends up on the toggle button.
*/
if (is_nested_button (self))
{
parent = get_parent_context (self);
self = parent;
}
GPtrArray *names = g_ptr_array_new ();
gtk_at_context_get_description_accumulate (self, names, TRUE);
if (names->len == 0)
{
g_ptr_array_unref (names);
g_clear_object (&parent);
return g_strdup ("");
}
GString *res = g_string_new ("");
g_string_append (res, g_ptr_array_index (names, 0));
for (guint i = 1; i < names->len; i++)
{
g_string_append (res, " ");
g_string_append (res, g_ptr_array_index (names, i));
}
g_ptr_array_unref (names);
g_clear_object (&parent);
return g_string_free (res, FALSE);
return gtk_at_context_get_text (self, GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, GTK_ACCESSIBLE_RELATION_DESCRIBED_BY);
}
void