|
9 | 9 | // found in the LICENSE.chromium file. |
10 | 10 |
|
11 | 11 | use crate::{ |
12 | | - AdapterCallback, Event, ObjectEvent, WindowEvent, |
| 12 | + AdapterCallback, CacheEvent, Event, ObjectEvent, WindowEvent, |
13 | 13 | context::{ActionHandlerNoMut, ActionHandlerWrapper, AppContext, Context}, |
14 | 14 | filters::filter, |
15 | 15 | node::{NodeIdOrRoot, NodeWrapper, PlatformNode, PlatformRoot}, |
@@ -58,6 +58,7 @@ impl<'a> AdapterChangeHandler<'a> { |
58 | 58 | let wrapper = NodeWrapper(node); |
59 | 59 | let interfaces = wrapper.interfaces(); |
60 | 60 | self.adapter.register_interfaces(node.id(), interfaces); |
| 61 | + self.adapter.emit_cache_added(node.id()); |
61 | 62 | if is_root && role == Role::Window { |
62 | 63 | let adapter_index = self |
63 | 64 | .adapter |
@@ -102,6 +103,7 @@ impl<'a> AdapterChangeHandler<'a> { |
102 | 103 | } |
103 | 104 | self.adapter |
104 | 105 | .emit_object_event(node.id(), ObjectEvent::StateChanged(State::Defunct, true)); |
| 106 | + self.adapter.emit_cache_removed(node.id()); |
105 | 107 | self.adapter |
106 | 108 | .unregister_interfaces(node.id(), wrapper.interfaces()); |
107 | 109 | if let Some(true) = node.is_selected() { |
@@ -508,6 +510,16 @@ impl Adapter { |
508 | 510 | .emit_event(self, Event::Object { target, event }); |
509 | 511 | } |
510 | 512 |
|
| 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 | + |
511 | 523 | pub fn set_root_window_bounds(&mut self, new_bounds: WindowBounds) { |
512 | 524 | let mut bounds = self.context.root_window_bounds.write().unwrap(); |
513 | 525 | *bounds = new_bounds; |
@@ -599,3 +611,212 @@ impl Drop for Adapter { |
599 | 611 | self.context.write_app_context().remove_adapter(self.id); |
600 | 612 | } |
601 | 613 | } |
| 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 | +} |
0 commit comments