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.
56 lines
2.0 KiB
Go
56 lines
2.0 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package atspi
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// A removed Chromium generated-text object can return zero characters before
|
|
// its removal event reaches us. Refresh its metadata and parent's child order
|
|
// rather than silently skipping text from an obsolete snapshot. Empty text
|
|
// alone does not prove removal, so keep identities and live siblings intact.
|
|
func (adapter *Adapter) document_text_became_empty(object ObjectID) error {
|
|
adapter.treeMutex.Lock()
|
|
tree := adapter.documentTree
|
|
adapter.treeMutex.Unlock()
|
|
if tree != nil {
|
|
tree.mu.RLock()
|
|
entry := tree.nodes[object]
|
|
tree.mu.RUnlock()
|
|
adapter.treeInvalidations.Add(uint64(tree.InvalidateSnapshot(object)))
|
|
if entry.hasParent {
|
|
adapter.treeInvalidations.Add(uint64(tree.MarkChildrenChanged(entry.parent)))
|
|
}
|
|
}
|
|
delete(adapter.documentLineCache, object)
|
|
return ErrStaleText
|
|
}
|
|
|
|
// libatspi forwards Chromium's missing-object response as an untyped GError.
|
|
// Match the queried path exactly; a timeout or unsupported caret is not proof
|
|
// that the virtual reading position has disappeared.
|
|
func (adapter *Adapter) document_object_error(object ObjectID, err error) error {
|
|
if err == nil || !object.Valid() || !strings.HasSuffix(strings.TrimSpace(err.Error()),
|
|
"could not be processed as object "+object.Path+" does not exist") {
|
|
return err
|
|
}
|
|
for _, tree := range adapter.ownedTrees() {
|
|
tree.mu.RLock()
|
|
entry := tree.nodes[object]
|
|
tree.mu.RUnlock()
|
|
adapter.treeInvalidations.Add(uint64(tree.Invalidate(object)))
|
|
if entry.hasParent {
|
|
// Virtualized pages can replace the enclosing post or feed, not
|
|
// just this target. Refresh every ancestor's child list so capture
|
|
// reaches the live replacement rather than reusing stale branches.
|
|
adapter.treeInvalidations.Add(uint64(tree.markAncestorChildrenChanged(entry.parent)))
|
|
}
|
|
}
|
|
clear(adapter.documentLineCache)
|
|
return fmt.Errorf("%w: %w", ErrDeadObject, err)
|
|
}
|