Found a few ways in which the Braille problem with browsers could resurface. Fixed now.

This commit is contained in:
Storm Dragon committed 2026-11-03 16:48:39 -05:00
1 parent 0a3d946ffa
commit a47253d697
13 files changed
+292 -23

No files matched your search

+38 -1
View File
@@ -43,6 +43,25 @@ import (
type embeddedTextBudget struct {
nodes, characters int
deadline time.Time
incomplete bool
}
// The display text may expand, but every offset continues to describe RawText.
// Keep the same bounded expansion and recovery rules for browse reads and
// native caret snapshots.
func expandNativeDocumentText(ctx context.Context, source *C.AtspiAccessible, snapshot Snapshot, line DocumentCaretText) DocumentCaretText {
line.RawText = line.Text
if snapshot.Editable || snapshot.Role == "password text" || snapshot.Role == "terminal" || !strings.ContainsRune(line.Text, '\ufffc') {
return line
}
deadline := time.Now().Add(100 * time.Millisecond)
if ctxDeadline, ok := ctx.Deadline(); ok && ctxDeadline.Before(deadline) {
deadline = ctxDeadline
}
budget := embeddedTextBudget{nodes: 32, characters: maxSnapshotFieldLength, deadline: deadline}
expanded := expandNativeEmbeddedText(source, line.Text, line.Start, 0, &budget)
line.applyExpandedText(expanded, snapshot.Name, budget.incomplete)
return line
}
func expandNativeEmbeddedText(source *C.AtspiAccessible, content string, start, depth int, budget *embeddedTextBudget) string {
@@ -51,6 +70,7 @@ func expandNativeEmbeddedText(source *C.AtspiAccessible, content string, start,
for _, char := range content {
offset++
if budget.characters <= 0 {
budget.incomplete = true
break
}
if char != '\ufffc' {
@@ -59,6 +79,7 @@ func expandNativeEmbeddedText(source *C.AtspiAccessible, content string, start,
continue
}
if depth >= 8 || budget.nodes <= 0 || time.Now().After(budget.deadline) {
budget.incomplete = true
continue
}
budget.nodes--
@@ -70,6 +91,7 @@ func expandNativeEmbeddedText(source *C.AtspiAccessible, content string, start,
func expandNativeEmbeddedChild(source *C.AtspiAccessible, offset, depth int, budget *embeddedTextBudget) string {
hypertext := C.atspi_accessible_get_hypertext_iface(source)
if hypertext == nil {
budget.incomplete = true
return ""
}
defer C.g_object_unref(C.gpointer(hypertext))
@@ -77,18 +99,22 @@ func expandNativeEmbeddedChild(source *C.AtspiAccessible, offset, depth int, bud
index := C.atspi_hypertext_get_link_index(hypertext, C.gint(offset), &nativeErr)
if nativeErr != nil {
C.g_error_free(nativeErr)
budget.incomplete = true
return ""
}
if index < 0 {
budget.incomplete = true
return ""
}
link := C.atspi_hypertext_get_link(hypertext, index, nil)
if link == nil {
budget.incomplete = true
return ""
}
defer C.g_object_unref(C.gpointer(link))
child := C.atspi_hyperlink_get_object(link, 0, nil)
if child == nil {
budget.incomplete = true
return ""
}
defer C.g_object_unref(C.gpointer(child))
@@ -98,23 +124,34 @@ func expandNativeEmbeddedChild(source *C.AtspiAccessible, offset, depth int, bud
}
text := C.atspi_accessible_get_text_iface(child)
if text == nil {
budget.incomplete = true
return ""
}
defer C.g_object_unref(C.gpointer(text))
count := C.atspi_text_get_character_count(text, nil)
if count <= 0 || time.Now().After(budget.deadline) {
budget.incomplete = true
return ""
}
if count > C.gint(maxSnapshotFieldLength) {
budget.incomplete = true
}
content := C.atspi_text_get_text(text, 0, min(count, C.gint(maxSnapshotFieldLength)), nil)
if content == nil {
budget.incomplete = true
return ""
}
defer C.g_free(C.gpointer(content))
length := int(C.review_text_length(content, C.int(maxSnapshotFieldLength*utf8.UTFMax)))
if length < 0 || length > maxSnapshotFieldLength*utf8.UTFMax {
budget.incomplete = true
return ""
}
expanded := expandNativeEmbeddedText(child, C.GoStringN(content, C.int(length)), 0, depth, budget)
value := C.GoStringN(content, C.int(length))
if !utf8.ValidString(value) || utf8.RuneCountInString(value) != min(int(count), maxSnapshotFieldLength) {
budget.incomplete = true
}
expanded := expandNativeEmbeddedText(child, value, 0, depth, budget)
switch role {
case C.ATSPI_ROLE_HEADING, C.ATSPI_ROLE_LIST, C.ATSPI_ROLE_LIST_ITEM, C.ATSPI_ROLE_PARAGRAPH,
C.ATSPI_ROLE_SECTION, C.ATSPI_ROLE_TABLE, C.ATSPI_ROLE_TABLE_CELL, C.ATSPI_ROLE_TABLE_ROW: