Reset master to testing content
This commit is contained in:
1 parent
0f2587b719
commit
42afa2a56c
1483 files changed
+145308
-938069
No files matched your search
@@ -0,0 +1,172 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package atspi
|
||||
|
||||
import "context"
|
||||
|
||||
// Owned by AccessibleTree under its mutex. Published data is immutable and can
|
||||
// outlive a replacement; ValidateDocumentRange rejects retired views. Dirty
|
||||
// flags stay in treeEntry, so an unrelated invalidation need not rebuild this.
|
||||
type documentData struct {
|
||||
nodes []DocumentNode
|
||||
indices map[ObjectID]int
|
||||
ends []int
|
||||
rows map[ObjectID][]ObjectReference
|
||||
}
|
||||
|
||||
func new_document_data(nodes []DocumentNode) *documentData {
|
||||
data := &documentData{nodes: nodes, indices: make(map[ObjectID]int, len(nodes)), ends: make([]int, len(nodes))}
|
||||
for index, node := range nodes {
|
||||
data.indices[node.Reference.ID()] = index
|
||||
data.ends[index] = index
|
||||
}
|
||||
for index := len(nodes) - 1; index > 0; index-- {
|
||||
parent := data.indices[nodes[index].Parent]
|
||||
data.ends[parent] = max(data.ends[parent], data.ends[index])
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// SameContent identifies a shared immutable view, not merely matching object
|
||||
// paths. A replacement with surviving identities still has different content.
|
||||
func (document DocumentSnapshot) SameContent(other DocumentSnapshot) bool {
|
||||
return document.data != nil && document.data == other.data
|
||||
}
|
||||
|
||||
func (document DocumentSnapshot) Index(id ObjectID) (int, bool) {
|
||||
if document.data != nil {
|
||||
index, ok := document.data.indices[id]
|
||||
return index, ok
|
||||
}
|
||||
for index, node := range document.Nodes {
|
||||
if node.Reference.ID() == id {
|
||||
return index, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (document DocumentSnapshot) Node(id ObjectID) (DocumentNode, bool) {
|
||||
index, ok := document.Index(id)
|
||||
if !ok {
|
||||
return DocumentNode{}, false
|
||||
}
|
||||
return document.Nodes[index], true
|
||||
}
|
||||
|
||||
func (document DocumentSnapshot) SubtreeEnd(index int) int {
|
||||
if document.data != nil {
|
||||
return document.data.ends[index]
|
||||
}
|
||||
end := index
|
||||
for next := index + 1; next < len(document.Nodes); next++ {
|
||||
parent, ok := document.Index(document.Nodes[next].Parent)
|
||||
if !ok || parent < index {
|
||||
break
|
||||
}
|
||||
end = next
|
||||
}
|
||||
return end
|
||||
}
|
||||
|
||||
func document_entry_error(entry treeEntry) error {
|
||||
switch {
|
||||
case entry.snapshotInvalid:
|
||||
return ErrStaleSnapshot
|
||||
case entry.childrenInvalid:
|
||||
return ErrIncompleteTree
|
||||
case entry.textInvalid:
|
||||
return ErrStaleText
|
||||
case entry.geometryInvalid:
|
||||
return ErrStaleGeometry
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateDocumentRange checks traversed objects, including skipped/hidden
|
||||
// candidates, before committing a move. Ancestor metadata always matters;
|
||||
// ancestor child order matters when crossing objects. A character move within
|
||||
// one live leaf therefore survives insertions elsewhere in its parent.
|
||||
func (tree *AccessibleTree) ValidateDocumentRange(ctx context.Context, document DocumentSnapshot, from, to int) error {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.validate_document_range_locked(ctx, document, from, to)
|
||||
}
|
||||
|
||||
// ValidateDocumentOrder also checks ancestor child order when a structural
|
||||
// search or a document-boundary jump will leave its current object.
|
||||
func (tree *AccessibleTree) ValidateDocumentOrder(ctx context.Context, document DocumentSnapshot, index int) error {
|
||||
tree.mu.RLock()
|
||||
defer tree.mu.RUnlock()
|
||||
return tree.validate_document_path_locked(ctx, document, index, index, true)
|
||||
}
|
||||
|
||||
func (tree *AccessibleTree) validate_document_range_locked(ctx context.Context, document DocumentSnapshot, from, to int) error {
|
||||
return tree.validate_document_path_locked(ctx, document, from, to, from != to)
|
||||
}
|
||||
|
||||
func (tree *AccessibleTree) validate_document_path_locked(ctx context.Context, document DocumentSnapshot, from, to int, requireOrder bool) error {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if document.data == nil || tree.documents[document.Context.TopLevel.ID()] != document.data {
|
||||
return ErrStaleObject
|
||||
}
|
||||
if from < 0 || to < 0 || from >= len(document.Nodes) || to >= len(document.Nodes) {
|
||||
return ErrStaleObject
|
||||
}
|
||||
for index := min(from, to); index <= max(from, to); index++ {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
node := document.Nodes[index]
|
||||
entry, err := tree.resolveLocked(node.Reference)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := document_entry_error(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tree.validate_document_row_locked(document, node.Reference.ID()); err != nil {
|
||||
return err
|
||||
}
|
||||
for entry.hasParent {
|
||||
entry = tree.nodes[entry.parent]
|
||||
if entry.snapshotInvalid {
|
||||
return ErrStaleSnapshot
|
||||
}
|
||||
if entry.geometryInvalid {
|
||||
return ErrStaleGeometry
|
||||
}
|
||||
if requireOrder && entry.childrenInvalid {
|
||||
return ErrIncompleteTree
|
||||
}
|
||||
// Hypertext ranges interleave parent-owned text with embedded
|
||||
// children. An insertion can change those ranges without changing
|
||||
// the accessible child list. Refresh before crossing objects, while
|
||||
// allowing movement within an unchanged child to remain local.
|
||||
if requireOrder && entry.textInvalid && len(entry.snapshot.TextChildren) != 0 {
|
||||
return ErrStaleText
|
||||
}
|
||||
if err := tree.validate_document_row_locked(document, entry.snapshot.ID()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tree *AccessibleTree) validate_document_row_locked(document DocumentSnapshot, id ObjectID) error {
|
||||
for _, reference := range document.data.rows[id] {
|
||||
entry, err := tree.resolveLocked(reference)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := document_entry_error(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user