// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later #include "adapter_native.h" #include static const gchar *content; static guint boundaryCalls, characterCalls, textCalls; static gboolean failProbe, changedText; guint atspi_text_get_character_at_offset(AtspiText *text, gint offset, GError **error) { characterCalls++; if (failProbe) { g_set_error_literal(error, g_quark_from_static_string("probe"), 1, "removed text"); return 0; } return offset >= 0 && offset < g_utf8_strlen(content, -1) ? g_utf8_get_char(g_utf8_offset_to_pointer(content, offset)) : 0; } gint atspi_text_get_character_count(AtspiText *text, GError **error) { return g_utf8_strlen(content, -1); } gchar *atspi_text_get_text(AtspiText *text, gint start, gint end, GError **error) { textCalls++; if (changedText) return g_strdup(""); g_assert_cmpint(start, >=, 0); g_assert_cmpint(end - start, <=, 4096); g_assert_cmpint(end, <=, g_utf8_strlen(content, -1)); const gchar *first = g_utf8_offset_to_pointer(content, start); return g_strndup(first, g_utf8_offset_to_pointer(content, end) - first); } AtspiTextRange *atspi_text_get_string_at_offset(AtspiText *text, gint offset, AtspiTextGranularity unit, GError **error) { boundaryCalls++; // Model the verified browser failure: asking for LINE at the final // ellipsis aborts inside the provider, before it can return a bad range. if (unit == ATSPI_TEXT_GRANULARITY_LINE && offset >= 0 && offset == g_utf8_strlen(content, -1) - 1) { g_assert_cmpuint(g_utf8_get_char(g_utf8_offset_to_pointer(content, offset)), !=, 0x2026); } AtspiTextRange *range = g_new0(AtspiTextRange, 1); range->end_offset = g_utf8_strlen(content, -1); range->content = g_strdup(content); return range; } static void assert_range(const gchar *value, gint offset, AtspiTextGranularity unit, gint start, gint end, const gchar *expected, guint calls) { content = value; boundaryCalls = characterCalls = textCalls = 0; GError *error = NULL; AtspiTextRange *range = cthulhu_text_get_string_at_offset(NULL, offset, unit, &error); g_assert_no_error(error); g_assert_nonnull(range); g_assert_cmpint(range->start_offset, ==, start); g_assert_cmpint(range->end_offset, ==, end); g_assert_cmpstr(range->content, ==, expected); g_assert_cmpuint(boundaryCalls, ==, calls); if (unit != ATSPI_TEXT_GRANULARITY_LINE || offset < 0) g_assert_cmpuint(characterCalls, ==, 0); g_assert_cmpuint(textCalls, ==, calls == 0 ? 1 : 0); g_free(range->content); g_free(range); } int main(void) { assert_range("Truncated text…", 14, ATSPI_TEXT_GRANULARITY_LINE, 0, 15, "Truncated text…", 0); assert_range("…", 0, ATSPI_TEXT_GRANULARITY_LINE, 0, 1, "…", 0); assert_range("First\n猫🐈…", 8, ATSPI_TEXT_GRANULARITY_LINE, 6, 9, "猫🐈…", 0); assert_range("Ordinary text", 12, ATSPI_TEXT_GRANULARITY_LINE, 0, 13, "Ordinary text", 1); assert_range("An… inside", 2, ATSPI_TEXT_GRANULARITY_LINE, 0, 10, "An… inside", 1); assert_range("Text…", 4, ATSPI_TEXT_GRANULARITY_CHAR, 0, 5, "Text…", 1); assert_range("Text…", 4, ATSPI_TEXT_GRANULARITY_WORD, 0, 5, "Text…", 1); assert_range("Text…", -1, ATSPI_TEXT_GRANULARITY_LINE, 0, 5, "Text…", 1); gchar *prefix = g_strnfill(5000, 'x'); gchar *longText = g_strconcat(prefix, "…", NULL); assert_range(longText, 5000, ATSPI_TEXT_GRANULARITY_LINE, 905, 5001, longText + 905, 0); g_free(prefix); g_free(longText); content = "Changed…"; boundaryCalls = 0; changedText = TRUE; GError *error = NULL; g_assert_null(cthulhu_text_get_string_at_offset(NULL, 7, ATSPI_TEXT_GRANULARITY_LINE, &error)); g_assert_no_error(error); g_assert_cmpuint(boundaryCalls, ==, 0); changedText = FALSE; failProbe = TRUE; g_assert_null(cthulhu_text_get_string_at_offset(NULL, 7, ATSPI_TEXT_GRANULARITY_LINE, &error)); g_assert_nonnull(error); g_assert_cmpuint(boundaryCalls, ==, 0); g_clear_error(&error); return 0; }