Skip to content

Commit 61bdabd

Browse files
authored
feat: Implement the cache object on Unix (#719)
1 parent c9725e2 commit 61bdabd

13 files changed

Lines changed: 970 additions & 24 deletions

File tree

platforms/atspi-common/src/adapter.rs

Lines changed: 222 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// found in the LICENSE.chromium file.
1010

1111
use crate::{
12-
AdapterCallback, Event, ObjectEvent, WindowEvent,
12+
AdapterCallback, CacheEvent, Event, ObjectEvent, WindowEvent,
1313
context::{ActionHandlerNoMut, ActionHandlerWrapper, AppContext, Context},
1414
filters::filter,
1515
node::{NodeIdOrRoot, NodeWrapper, PlatformNode, PlatformRoot},
@@ -58,6 +58,7 @@ impl<'a> AdapterChangeHandler<'a> {
5858
let wrapper = NodeWrapper(node);
5959
let interfaces = wrapper.interfaces();
6060
self.adapter.register_interfaces(node.id(), interfaces);
61+
self.adapter.emit_cache_added(node.id());
6162
if is_root && role == Role::Window {
6263
let adapter_index = self
6364
.adapter
@@ -102,6 +103,7 @@ impl<'a> AdapterChangeHandler<'a> {
102103
}
103104
self.adapter
104105
.emit_object_event(node.id(), ObjectEvent::StateChanged(State::Defunct, true));
106+
self.adapter.emit_cache_removed(node.id());
105107
self.adapter
106108
.unregister_interfaces(node.id(), wrapper.interfaces());
107109
if let Some(true) = node.is_selected() {
@@ -508,6 +510,16 @@ impl Adapter {
508510
.emit_event(self, Event::Object { target, event });
509511
}
510512

513+
fn emit_cache_added(&self, target: NodeId) {
514+
self.callback
515+
.emit_event(self, Event::Cache(CacheEvent::Added(target)));
516+
}
517+
518+
fn emit_cache_removed(&self, target: NodeId) {
519+
self.callback
520+
.emit_event(self, Event::Cache(CacheEvent::Removed(target)));
521+
}
522+
511523
pub fn set_root_window_bounds(&mut self, new_bounds: WindowBounds) {
512524
let mut bounds = self.context.root_window_bounds.write().unwrap();
513525
*bounds = new_bounds;
@@ -599,3 +611,212 @@ impl Drop for Adapter {
599611
self.context.write_app_context().remove_adapter(self.id);
600612
}
601613
}
614+
615+
#[cfg(test)]
616+
mod tests {
617+
use super::Adapter;
618+
use crate::{AdapterCallback, AppContext, CacheEvent, Event, InterfaceSet, WindowBounds};
619+
use accesskit::{
620+
ActionHandler, ActionRequest, Node, NodeId as LocalNodeId, Role, Tree, TreeId, TreeUpdate,
621+
};
622+
use accesskit_consumer::NodeId;
623+
use std::sync::{Arc, Mutex};
624+
625+
#[derive(Clone, Copy, Debug, PartialEq)]
626+
enum CacheOp {
627+
Added(NodeId),
628+
Removed(NodeId),
629+
}
630+
631+
struct CapturingCallback {
632+
ops: Arc<Mutex<Vec<CacheOp>>>,
633+
}
634+
635+
impl AdapterCallback for CapturingCallback {
636+
fn register_interfaces(&self, _: &Adapter, _: NodeId, _: InterfaceSet) {}
637+
fn unregister_interfaces(&self, _: &Adapter, _: NodeId, _: InterfaceSet) {}
638+
fn emit_event(&self, _: &Adapter, event: Event) {
639+
let mut ops = self.ops.lock().unwrap();
640+
match event {
641+
Event::Cache(CacheEvent::Added(id)) => ops.push(CacheOp::Added(id)),
642+
Event::Cache(CacheEvent::Removed(id)) => ops.push(CacheOp::Removed(id)),
643+
_ => {}
644+
}
645+
}
646+
}
647+
648+
struct NoOpActionHandler;
649+
impl ActionHandler for NoOpActionHandler {
650+
fn do_action(&mut self, _request: ActionRequest) {}
651+
}
652+
653+
fn with_children(role: Role, children: &[LocalNodeId]) -> Node {
654+
let mut node = Node::new(role);
655+
node.set_children(children.to_vec());
656+
node
657+
}
658+
659+
fn build(initial: TreeUpdate) -> (Adapter, Arc<Mutex<Vec<CacheOp>>>) {
660+
let ops = Arc::new(Mutex::new(Vec::new()));
661+
let app_context = AppContext::new(None);
662+
let adapter = Adapter::new(
663+
&app_context,
664+
CapturingCallback { ops: ops.clone() },
665+
initial,
666+
false,
667+
WindowBounds::default(),
668+
NoOpActionHandler,
669+
);
670+
(adapter, ops)
671+
}
672+
673+
fn initial_tree() -> TreeUpdate {
674+
TreeUpdate {
675+
nodes: vec![
676+
(
677+
LocalNodeId(0),
678+
with_children(Role::Window, &[LocalNodeId(1)]),
679+
),
680+
(LocalNodeId(1), Node::new(Role::Button)),
681+
],
682+
tree: Some(Tree::new(LocalNodeId(0))),
683+
tree_id: TreeId::ROOT,
684+
focus: LocalNodeId(0),
685+
}
686+
}
687+
688+
fn update(nodes: Vec<(LocalNodeId, Node)>) -> TreeUpdate {
689+
TreeUpdate {
690+
nodes,
691+
tree: None,
692+
tree_id: TreeId::ROOT,
693+
focus: LocalNodeId(0),
694+
}
695+
}
696+
697+
#[test]
698+
fn no_cache_events_on_construction() {
699+
let (_adapter, ops) = build(initial_tree());
700+
assert!(ops.lock().unwrap().is_empty());
701+
}
702+
703+
#[test]
704+
fn add_node_emits_one_added() {
705+
let (mut adapter, ops) = build(initial_tree());
706+
ops.lock().unwrap().clear();
707+
adapter.update(update(vec![
708+
(
709+
LocalNodeId(0),
710+
with_children(Role::Window, &[LocalNodeId(1), LocalNodeId(2)]),
711+
),
712+
(LocalNodeId(2), Node::new(Role::Button)),
713+
]));
714+
let ops = ops.lock().unwrap();
715+
assert_eq!(ops.len(), 1);
716+
assert!(matches!(ops[0], CacheOp::Added(_)));
717+
}
718+
719+
#[test]
720+
fn remove_node_emits_removed_for_same_id() {
721+
let (mut adapter, ops) = build(initial_tree());
722+
adapter.update(update(vec![
723+
(
724+
LocalNodeId(0),
725+
with_children(Role::Window, &[LocalNodeId(1), LocalNodeId(2)]),
726+
),
727+
(LocalNodeId(2), Node::new(Role::Button)),
728+
]));
729+
let added_id = match ops.lock().unwrap().as_slice() {
730+
[CacheOp::Added(id)] => *id,
731+
other => panic!("expected exactly one Added, got {other:?}"),
732+
};
733+
ops.lock().unwrap().clear();
734+
adapter.update(update(vec![(
735+
LocalNodeId(0),
736+
with_children(Role::Window, &[LocalNodeId(1)]),
737+
)]));
738+
assert_eq!(*ops.lock().unwrap(), vec![CacheOp::Removed(added_id)]);
739+
}
740+
741+
#[test]
742+
fn subtree_add_emits_added_per_node() {
743+
let (mut adapter, ops) = build(initial_tree());
744+
ops.lock().unwrap().clear();
745+
adapter.update(update(vec![
746+
(
747+
LocalNodeId(0),
748+
with_children(Role::Window, &[LocalNodeId(1), LocalNodeId(2)]),
749+
),
750+
(
751+
LocalNodeId(2),
752+
with_children(Role::Group, &[LocalNodeId(3), LocalNodeId(4)]),
753+
),
754+
(LocalNodeId(3), Node::new(Role::Button)),
755+
(LocalNodeId(4), Node::new(Role::Button)),
756+
]));
757+
let ops = ops.lock().unwrap();
758+
assert_eq!(ops.len(), 3);
759+
assert!(ops.iter().all(|op| matches!(op, CacheOp::Added(_))));
760+
}
761+
762+
#[test]
763+
fn subtree_remove_emits_removed_per_node() {
764+
let (mut adapter, ops) = build(initial_tree());
765+
adapter.update(update(vec![
766+
(
767+
LocalNodeId(0),
768+
with_children(Role::Window, &[LocalNodeId(1), LocalNodeId(2)]),
769+
),
770+
(
771+
LocalNodeId(2),
772+
with_children(Role::Group, &[LocalNodeId(3), LocalNodeId(4)]),
773+
),
774+
(LocalNodeId(3), Node::new(Role::Button)),
775+
(LocalNodeId(4), Node::new(Role::Button)),
776+
]));
777+
ops.lock().unwrap().clear();
778+
adapter.update(update(vec![(
779+
LocalNodeId(0),
780+
with_children(Role::Window, &[LocalNodeId(1)]),
781+
)]));
782+
let ops = ops.lock().unwrap();
783+
assert_eq!(ops.len(), 3);
784+
assert!(ops.iter().all(|op| matches!(op, CacheOp::Removed(_))));
785+
}
786+
787+
#[test]
788+
fn filter_transition_into_tree_emits_added() {
789+
let mut hidden = Node::new(Role::Button);
790+
hidden.set_hidden();
791+
let (mut adapter, ops) = build(TreeUpdate {
792+
nodes: vec![
793+
(
794+
LocalNodeId(0),
795+
with_children(Role::Window, &[LocalNodeId(1), LocalNodeId(2)]),
796+
),
797+
(LocalNodeId(1), Node::new(Role::Button)),
798+
(LocalNodeId(2), hidden),
799+
],
800+
tree: Some(Tree::new(LocalNodeId(0))),
801+
tree_id: TreeId::ROOT,
802+
focus: LocalNodeId(0),
803+
});
804+
ops.lock().unwrap().clear();
805+
adapter.update(update(vec![(LocalNodeId(2), Node::new(Role::Button))]));
806+
let ops = ops.lock().unwrap();
807+
assert_eq!(ops.len(), 1);
808+
assert!(matches!(ops[0], CacheOp::Added(_)));
809+
}
810+
811+
#[test]
812+
fn filter_transition_out_of_tree_emits_removed() {
813+
let (mut adapter, ops) = build(initial_tree());
814+
ops.lock().unwrap().clear();
815+
let mut hidden = Node::new(Role::Button);
816+
hidden.set_hidden();
817+
adapter.update(update(vec![(LocalNodeId(1), hidden)]));
818+
let ops = ops.lock().unwrap();
819+
assert_eq!(ops.len(), 1);
820+
assert!(matches!(ops[0], CacheOp::Removed(_)));
821+
}
822+
}

platforms/atspi-common/src/events.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ pub enum Event {
1919
name: String,
2020
event: WindowEvent,
2121
},
22+
Cache(CacheEvent),
23+
}
24+
25+
#[derive(Debug)]
26+
pub enum CacheEvent {
27+
Added(NodeId),
28+
Removed(NodeId),
2229
}
2330

2431
#[derive(Debug)]

platforms/atspi-common/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,6 @@ pub use callback::AdapterCallback;
2828
pub use context::{ActionHandlerNoMut, ActionHandlerWrapper, AppContext};
2929
pub use error::*;
3030
pub use events::*;
31-
pub use node::{NodeIdOrRoot, PlatformNode, PlatformRoot};
31+
pub use node::{CacheNode, NodeIdOrRoot, PlatformNode, PlatformRoot};
3232
pub use rect::*;
3333
pub use util::WindowBounds;

0 commit comments

Comments
 (0)