Make web document navigation survive live-updating pages
Recreated-node caret recovery, tolerant caret validation, per-root retained document trees and cursors across tab switches, document warm-up, coalesced structural moves, and hung-provider fast-fail with an 'Application is not responding' announcement.
This commit is contained in:
1 parent
8ce5e5cdad
commit
7eef6e8be6
30 files changed
+1484
-174
No files matched your search
@@ -12,19 +12,29 @@ import (
|
||||
"git.stormux.org/storm/cthulhu/internal/atspi"
|
||||
)
|
||||
|
||||
// Text and geometry staleness used to force a recapture before a caret move
|
||||
// could commit onto the dirty target. On constantly ticking pages the
|
||||
// target (and everything between caret and target) is re-invalidated faster
|
||||
// than capture-and-retry can run, which trapped the caret outright. Caret
|
||||
// moves read text and line bounds live, so the live read now wins for those
|
||||
// two; semantic snapshot staleness (names are spoken from the snapshot)
|
||||
// still refreshes first.
|
||||
func TestDocumentCaretDefersUnrelatedUpdatesUntilTraversed(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
invalidate func(*atspi.AccessibleTree)
|
||||
want error
|
||||
}{
|
||||
{"text", func(tree *atspi.AccessibleTree) { tree.InvalidateText(webID("/next")) }, atspi.ErrStaleText},
|
||||
{"text", func(tree *atspi.AccessibleTree) { tree.InvalidateText(webID("/next")) }, nil},
|
||||
{"semantics", func(tree *atspi.AccessibleTree) { tree.InvalidateSnapshot(webID("/next")) }, atspi.ErrStaleSnapshot},
|
||||
{"geometry", func(tree *atspi.AccessibleTree) { tree.InvalidateGeometry(webID("/next")) }, atspi.ErrStaleGeometry},
|
||||
{"geometry", func(tree *atspi.AccessibleTree) { tree.InvalidateGeometry(webID("/next")) }, nil},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
reader := caretTextReader{webID("/text"): "Alpha", webID("/next"): "Next"}
|
||||
cursor := newCaretTextCursor(t, reader)
|
||||
// The provider text moves on after capture; the live read must
|
||||
// be what the move reports, not the captured length or text.
|
||||
reader[webID("/next")] = "Next updated"
|
||||
test.invalidate(cursor.tree)
|
||||
if _, err := cursor.SyncFocus(context.Background(), webID("/text")); err != nil {
|
||||
t.Fatal("unrelated update blocked focus synchronization", err)
|
||||
@@ -33,7 +43,13 @@ func TestDocumentCaretDefersUnrelatedUpdatesUntilTraversed(t *testing.T) {
|
||||
if err != nil || !move.Moved || move.Text != "l" {
|
||||
t.Fatalf("local character move: %+v %v", move, err)
|
||||
}
|
||||
_, err = cursor.MoveCaret(context.Background(), reader, CaretNextLine)
|
||||
move, err = cursor.MoveCaret(context.Background(), reader, CaretNextLine)
|
||||
if test.want == nil {
|
||||
if err != nil || !move.Moved || move.Text != "Next updated" || move.Position.Object.ID() != webID("/next") {
|
||||
t.Fatalf("live target read: %+v %v, want Next updated", move, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if !errors.Is(err, test.want) {
|
||||
t.Fatalf("dirty target read: %v, want %v", err, test.want)
|
||||
}
|
||||
@@ -44,6 +60,9 @@ func TestDocumentCaretDefersUnrelatedUpdatesUntilTraversed(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Crossing now follows captured child order with generation-checked
|
||||
// endpoints and live reads; structural navigation still refreshes stale
|
||||
// child order before promising the next target.
|
||||
func TestDocumentCaretKeepsLocalTextThroughParentInsertion(t *testing.T) {
|
||||
reader := caretTextReader{webID("/text"): "Alpha", webID("/next"): "Next"}
|
||||
cursor := newCaretTextCursor(t, reader)
|
||||
@@ -52,8 +71,9 @@ func TestDocumentCaretKeepsLocalTextThroughParentInsertion(t *testing.T) {
|
||||
if err != nil || !move.Moved || move.Text != "l" {
|
||||
t.Fatalf("parent insertion blocked live text: %+v %v", move, err)
|
||||
}
|
||||
if _, err := cursor.MoveCaret(context.Background(), reader, CaretNextLine); !errors.Is(err, atspi.ErrIncompleteTree) {
|
||||
t.Fatal("crossed stale child order", err)
|
||||
move, err = cursor.MoveCaret(context.Background(), reader, CaretNextLine)
|
||||
if err != nil || !move.Moved || move.Text != "Next" || move.Position.Object.ID() != webID("/next") {
|
||||
t.Fatalf("crossing changed child order: %+v %v", move, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user