// 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 } adapter.treeMutex.Lock() trees := []*AccessibleTree{adapter.tree, adapter.documentTree, adapter.documentTextTree} adapter.treeMutex.Unlock() for _, tree := range trees { if tree == nil { continue } 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) }