Web fixes.

This commit is contained in:
ak47 committed 2026-11-04 21:22:51 -05:00
1 parent 914d174925
commit 453206b85b
11 files changed
+643 -25

No files matched your search

+53 -11
View File
@@ -8,6 +8,56 @@ static gchar *cthulhu_capture_text(AtspiAccessible *source, gchar **beforeCaret,
static void cthulhu_capture_document_metrics(AtspiAccessible *source, cthulhu_snapshot *snapshot);
static gchar *cthulhu_table_cell_label(AtspiAccessible *source, guint depth, guint *remaining);
// Chromium exposes doDefault even on inert text. Only an anchor with a label
// explicitly offering ancestor activation supplies evidence of a custom action.
// Never turn every descendant of a delegated click handler into a clickable.
static gboolean cthulhu_anchor_has_click_label(AtspiAccessible *source) {
gint count = atspi_accessible_get_child_count(source, NULL);
for (gint index = 0; index < MIN(count, 16); index++) {
AtspiAccessible *child = atspi_accessible_get_child_at_index(source, index, NULL);
if (child == NULL) continue;
AtspiRole role = atspi_accessible_get_role(child, NULL);
gboolean label = role == ATSPI_ROLE_STATIC || role == ATSPI_ROLE_TEXT;
gboolean found = FALSE;
AtspiStateSet *states = atspi_accessible_get_state_set(child);
gboolean passive = states != NULL &&
!atspi_state_set_contains(states, ATSPI_STATE_FOCUSABLE) &&
!atspi_state_set_contains(states, ATSPI_STATE_EDITABLE);
if (states != NULL) g_object_unref(states);
if (passive && label) {
AtspiAction *action = atspi_accessible_get_action_iface(child);
if (action != NULL) {
gint actions = MIN(atspi_action_get_n_actions(action, NULL), 16);
for (gint i = 0; i < actions && !found; i++) {
gchar *name = atspi_action_get_name(action, i, NULL);
found = name != NULL && g_ascii_strcasecmp(name, "clickAncestor") == 0;
g_free(name);
}
g_object_unref(action);
}
}
g_object_unref(child);
if (found) return TRUE;
}
return FALSE;
}
// Shared by capture and activation so a discovered target can actually be
// invoked. Lower numbers retain the existing preference for direct actions.
gint cthulhu_document_action_priority(AtspiAccessible *source, const gchar *name, gboolean anchor) {
if (name == NULL) return 0;
const gchar *direct[] = {"click", "click-ancestor", "press", "jump", "open", "activate"};
for (guint index = 0; index < G_N_ELEMENTS(direct); index++) {
if (g_ascii_strcasecmp(name, direct[index]) == 0) return index + 1;
}
if (!anchor) return 0;
if (g_ascii_strcasecmp(name, "clickAncestor") == 0) return 7;
if (g_ascii_strcasecmp(name, "doDefault") == 0) {
if (cthulhu_anchor_has_click_label(source)) return 8;
}
return 0;
}
// Some toolkits expose an edit field's label only as a relation, leaving its
// accessible name empty. Follow direct targets only, with a shared query and
// output budget; never infer a label from neighbouring controls or field text.
@@ -590,22 +640,14 @@ cthulhu_snapshot cthulhu_capture_snapshot(AtspiAccessible *source, gboolean capt
if (error != NULL) {
g_clear_error(&error);
}
for (gint index = 0; index < actionCount; index++) {
for (gint index = 0; index < MIN(actionCount, 64); index++) {
error = NULL;
gchar *actionName = atspi_action_get_name(action, index, &error);
if (error != NULL) {
g_clear_error(&error);
}
// Web providers use different action names for activation. Keep
// this list aligned with activateNativeDocumentObject so browse
// Enter can reach objects exposing click-ancestor or press.
if (actionName != NULL &&
(g_ascii_strcasecmp(actionName, "click") == 0 ||
g_ascii_strcasecmp(actionName, "click-ancestor") == 0 ||
g_ascii_strcasecmp(actionName, "press") == 0 ||
g_ascii_strcasecmp(actionName, "jump") == 0 ||
g_ascii_strcasecmp(actionName, "open") == 0 ||
g_ascii_strcasecmp(actionName, "activate") == 0)) {
if (cthulhu_document_action_priority(source, actionName,
g_strcmp0(snapshot.htmlTag, "a") == 0) != 0) {
snapshot.supportsClick = TRUE;
g_free(actionName);
break;
+11 -9
View File
@@ -13,7 +13,6 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"
"unicode/utf8"
"unsafe"
@@ -109,7 +108,7 @@ func (adapter *Adapter) SetDocumentPosition(ctx context.Context, position Object
}
// CaptureDocumentSoundGeometry refreshes only the presented object and its
// optional linked heading target, after navigation has scrolled them into view.
// optional semantic link target, after navigation has scrolled them into view.
// It never changes the cached document semantics or retains new native handles.
func (adapter *Adapter) CaptureDocumentSoundGeometry(ctx context.Context, object, link ObjectReference) ([2]SoundGeometry, error) {
if !object.Valid() {
@@ -231,7 +230,6 @@ func (adapter *Adapter) activateNativeDocumentObject(ctx context.Context, positi
return errors.New("current document object has no accessible action")
}
defer C.g_object_unref(C.gpointer(action))
names := []string{"click", "click-ancestor", "press", "jump", "open", "activate"}
var nativeErr *C.GError
count := int(C.atspi_action_get_n_actions(action, &nativeErr))
if nativeErr != nil {
@@ -241,18 +239,22 @@ func (adapter *Adapter) activateNativeDocumentObject(ctx context.Context, positi
}
// Read each action name once. Previously each preferred spelling caused
// another full round of synchronous accessibility calls.
actionNames := make([]string, min(max(count, 0), 64))
for index := range actionNames {
priorities := make([]int, min(max(count, 0), 64))
anchor := C.gboolean(0)
if snapshot.HTMLTag == "a" {
anchor = 1
}
for index := range priorities {
if err := ctx.Err(); err != nil {
return err
}
name := C.atspi_action_get_name(action, C.gint(index), nil)
actionNames[index] = C.GoString(name)
priorities[index] = int(C.cthulhu_document_action_priority(source, name, anchor))
C.g_free(C.gpointer(name))
}
for _, wanted := range names {
for index, name := range actionNames {
if !strings.EqualFold(name, wanted) {
for wanted := 1; wanted <= 8; wanted++ {
for index, priority := range priorities {
if priority != wanted {
continue
}
if err := ctx.Err(); err != nil {
+1
View File
@@ -93,6 +93,7 @@ typedef struct {
gchar *cthulhu_table_context(AtspiAccessible *source, gint *rows, gint *columns,
gchar **tableName, gchar **tablePath,
gint *rowIndex, gint *columnIndex, gboolean *guiTable);
gint cthulhu_document_action_priority(AtspiAccessible *source, const gchar *name, gboolean anchor);
cthulhu_snapshot cthulhu_capture_snapshot(AtspiAccessible *source, gboolean captureText,
gboolean captureCaretCharacter,
gboolean captureDocumentMetadata,
+8
View File
@@ -250,6 +250,14 @@ func captureNativeTree(ctx context.Context, root *C.AtspiAccessible, reviewBusNa
// A visibility change can reveal children omitted by an earlier review
// capture, even when the provider emits no children-changed event.
cachedChildren := known && !cached.childrenInvalid && !cached.childrenUnavailable && (!reviewOnly || !cached.geometryInvalid)
if document && snapshot.Role == "internal frame" {
// Chromium can replace a sibling iframe document without an
// invalidation for its retained frame object. Refresh this boundary
// on recapture so a successful header action cannot leave the old
// content document pinned in either our cache or libatspi's cache.
cachedChildren = false
C.atspi_accessible_clear_cache_single(source)
}
childrenUnavailable := false
for _, id := range cached.children {
if cache.sources[id] == nil {