Fix panic when querying available actions

Antonio Scandurra created

Change summary

crates/editor2/src/element.rs    |  12 
crates/gpui2/src/key_dispatch.rs | 189 +++++-----------------
crates/gpui2/src/window.rs       | 279 ++++++++++++++++++++++++---------
3 files changed, 249 insertions(+), 231 deletions(-)

Detailed changes

crates/editor2/src/element.rs 🔗

@@ -15,12 +15,12 @@ use crate::{
 use anyhow::Result;
 use collections::{BTreeMap, HashMap};
 use gpui::{
-    black, hsla, point, px, relative, size, transparent_black, Action, ActionListener, AnyElement,
-    AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase,
-    Edges, Element, ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla,
-    InputHandler, KeyContext, KeyDownEvent, KeyMatch, Line, LineLayout, Modifiers, MouseButton,
-    MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size,
-    Style, TextRun, TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout,
+    black, hsla, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
+    BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element,
+    ElementId, ElementInputHandler, Entity, FocusHandle, GlobalElementId, Hsla, InputHandler,
+    KeyContext, KeyDownEvent, KeyMatch, Line, LineLayout, Modifiers, MouseButton, MouseDownEvent,
+    MouseMoveEvent, MouseUpEvent, Pixels, ScrollWheelEvent, ShapedGlyph, Size, Style, TextRun,
+    TextStyle, TextSystem, ViewContext, WindowContext, WrappedLineLayout,
 };
 use itertools::Itertools;
 use language::language_settings::ShowWhitespaceSetting;

crates/gpui2/src/key_dispatch.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     build_action_from_type, Action, Bounds, DispatchPhase, Element, FocusEvent, FocusHandle,
-    FocusId, KeyContext, KeyDownEvent, KeyMatch, Keymap, KeystrokeMatcher, MouseDownEvent, Pixels,
+    FocusId, KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, MouseDownEvent, Pixels,
     Style, StyleRefinement, ViewContext, WindowContext,
 };
 use collections::HashMap;
@@ -9,11 +9,11 @@ use refineable::Refineable;
 use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
+    rc::Rc,
     sync::Arc,
 };
 use util::ResultExt;
 
-type KeyListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>;
 pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
 pub type FocusListener<V> =
     Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>;
@@ -21,7 +21,7 @@ pub type FocusListener<V> =
 #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
 pub struct DispatchNodeId(usize);
 
-pub struct KeyDispatcher {
+pub(crate) struct DispatchTree {
     node_stack: Vec<DispatchNodeId>,
     context_stack: Vec<KeyContext>,
     nodes: Vec<DispatchNode>,
@@ -31,19 +31,22 @@ pub struct KeyDispatcher {
 }
 
 #[derive(Default)]
-pub struct DispatchNode {
-    key_listeners: SmallVec<[KeyListener; 2]>,
-    action_listeners: SmallVec<[ActionListener; 16]>,
-    context: KeyContext,
+pub(crate) struct DispatchNode {
+    pub key_listeners: SmallVec<[KeyListener; 2]>,
+    pub action_listeners: SmallVec<[ActionListener; 16]>,
+    pub context: KeyContext,
     parent: Option<DispatchNodeId>,
 }
 
-struct ActionListener {
-    action_type: TypeId,
-    listener: Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
+type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>;
+
+#[derive(Clone)]
+pub(crate) struct ActionListener {
+    pub(crate) action_type: TypeId,
+    pub(crate) listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
 }
 
-impl KeyDispatcher {
+impl DispatchTree {
     pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
         Self {
             node_stack: Vec::new(),
@@ -97,7 +100,7 @@ impl KeyDispatcher {
     pub fn on_action(
         &mut self,
         action_type: TypeId,
-        listener: Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
+        listener: Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>,
     ) {
         self.active_node().action_listeners.push(ActionListener {
             action_type,
@@ -140,143 +143,40 @@ impl KeyDispatcher {
         actions
     }
 
-    pub fn dispatch_key(&mut self, target: FocusId, event: &dyn Any, cx: &mut WindowContext) {
-        if let Some(target_node_id) = self.focusable_node_ids.get(&target).copied() {
-            self.dispatch_key_on_node(target_node_id, event, cx);
-        }
-    }
-
-    fn dispatch_key_on_node(
+    pub fn dispatch_key(
         &mut self,
-        node_id: DispatchNodeId,
-        event: &dyn Any,
-        cx: &mut WindowContext,
-    ) {
-        let dispatch_path = self.dispatch_path(node_id);
-
-        // Capture phase
-        self.context_stack.clear();
-        cx.propagate_event = true;
-
-        for node_id in &dispatch_path {
-            let node = &self.nodes[node_id.0];
-            if !node.context.is_empty() {
-                self.context_stack.push(node.context.clone());
-            }
-
-            for key_listener in &node.key_listeners {
-                key_listener(event, DispatchPhase::Capture, cx);
-                if !cx.propagate_event {
-                    return;
-                }
-            }
+        keystroke: &Keystroke,
+        context: &[KeyContext],
+    ) -> Option<Box<dyn Action>> {
+        if !self
+            .keystroke_matchers
+            .contains_key(self.context_stack.as_slice())
+        {
+            let keystroke_contexts = self.context_stack.iter().cloned().collect();
+            self.keystroke_matchers.insert(
+                keystroke_contexts,
+                KeystrokeMatcher::new(self.keymap.clone()),
+            );
         }
 
-        // Bubble phase
-        for node_id in dispatch_path.iter().rev() {
-            let node = &self.nodes[node_id.0];
-
-            // Handle low level key events
-            for key_listener in &node.key_listeners {
-                key_listener(event, DispatchPhase::Bubble, cx);
-                if !cx.propagate_event {
-                    return;
-                }
-            }
-
-            // Match keystrokes
-            if !node.context.is_empty() {
-                if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
-                    if !self
-                        .keystroke_matchers
-                        .contains_key(self.context_stack.as_slice())
-                    {
-                        let keystroke_contexts = self.context_stack.iter().cloned().collect();
-                        self.keystroke_matchers.insert(
-                            keystroke_contexts,
-                            KeystrokeMatcher::new(self.keymap.clone()),
-                        );
-                    }
-
-                    let keystroke_matcher = self
-                        .keystroke_matchers
-                        .get_mut(self.context_stack.as_slice())
-                        .unwrap();
-                    if let KeyMatch::Some(action) = keystroke_matcher
-                        .match_keystroke(&key_down_event.keystroke, self.context_stack.as_slice())
-                    {
-                        // Clear all pending keystrokes when an action has been found.
-                        for keystroke_matcher in self.keystroke_matchers.values_mut() {
-                            keystroke_matcher.clear_pending();
-                        }
-
-                        self.dispatch_action_on_node(*node_id, action, cx);
-                        if !cx.propagate_event {
-                            return;
-                        }
-                    }
-                }
-
-                self.context_stack.pop();
+        let keystroke_matcher = self
+            .keystroke_matchers
+            .get_mut(self.context_stack.as_slice())
+            .unwrap();
+        if let KeyMatch::Some(action) = keystroke_matcher.match_keystroke(keystroke, context) {
+            // Clear all pending keystrokes when an action has been found.
+            for keystroke_matcher in self.keystroke_matchers.values_mut() {
+                keystroke_matcher.clear_pending();
             }
-        }
-    }
 
-    pub fn dispatch_action(
-        &self,
-        target: FocusId,
-        action: Box<dyn Action>,
-        cx: &mut WindowContext,
-    ) {
-        if let Some(target_node_id) = self.focusable_node_ids.get(&target).copied() {
-            self.dispatch_action_on_node(target_node_id, action, cx);
+            Some(action)
+        } else {
+            None
         }
     }
 
-    fn dispatch_action_on_node(
-        &self,
-        node_id: DispatchNodeId,
-        action: Box<dyn Action>,
-        cx: &mut WindowContext,
-    ) {
-        let dispatch_path = self.dispatch_path(node_id);
-
-        // Capture phase
-        for node_id in &dispatch_path {
-            let node = &self.nodes[node_id.0];
-            for ActionListener {
-                action_type,
-                listener,
-            } in &node.action_listeners
-            {
-                let any_action = action.as_any();
-                if *action_type == any_action.type_id() {
-                    listener(any_action, DispatchPhase::Capture, cx);
-                    if !cx.propagate_event {
-                        return;
-                    }
-                }
-            }
-        }
-
-        // Bubble phase
-        for node_id in dispatch_path.iter().rev() {
-            let node = &self.nodes[node_id.0];
-            for ActionListener {
-                action_type,
-                listener,
-            } in &node.action_listeners
-            {
-                let any_action = action.as_any();
-                if *action_type == any_action.type_id() {
-                    cx.propagate_event = false; // Actions stop propagation by default during the bubble phase
-                    listener(any_action, DispatchPhase::Bubble, cx);
-                    if !cx.propagate_event {
-                        return;
-                    }
-                }
-            }
-        }
+    pub fn node(&self, node_id: DispatchNodeId) -> &DispatchNode {
+        &self.nodes[node_id.0]
     }
 
     fn active_node(&mut self) -> &mut DispatchNode {
@@ -288,8 +188,7 @@ impl KeyDispatcher {
         *self.node_stack.last().unwrap()
     }
 
-    /// Returns the DispatchNodeIds from the root of the tree to the given target node id.
-    fn dispatch_path(&self, target: DispatchNodeId) -> SmallVec<[DispatchNodeId; 32]> {
+    pub fn dispatch_path(&self, target: DispatchNodeId) -> SmallVec<[DispatchNodeId; 32]> {
         let mut dispatch_path: SmallVec<[DispatchNodeId; 32]> = SmallVec::new();
         let mut current_node_id = Some(target);
         while let Some(node_id) = current_node_id {
@@ -299,6 +198,10 @@ impl KeyDispatcher {
         dispatch_path.reverse(); // Reverse the path so it goes from the root to the focused node.
         dispatch_path
     }
+
+    pub fn focusable_node_id(&self, target: FocusId) -> Option<DispatchNodeId> {
+        self.focusable_node_ids.get(&target).copied()
+    }
 }
 
 pub trait KeyDispatch<V: 'static>: 'static {

crates/gpui2/src/window.rs 🔗

@@ -1,14 +1,15 @@
 use crate::{
-    px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
-    Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DisplayId, Edges, Effect,
-    Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId,
-    Hsla, ImageData, InputEvent, IsZero, KeyContext, KeyDispatcher, LayoutId, Model, ModelContext,
-    Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path,
-    Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
-    PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
-    RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
-    Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
-    WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+    key_dispatch::ActionListener, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext,
+    AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
+    DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
+    EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData,
+    InputEvent, IsZero, KeyContext, KeyDownEvent, LayoutId, Model, ModelContext, Modifiers,
+    MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels,
+    PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite,
+    PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
+    SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription,
+    TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView,
+    WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::{anyhow, Result};
 use collections::HashMap;
@@ -89,9 +90,7 @@ impl FocusId {
     pub(crate) fn contains(&self, other: Self, cx: &WindowContext) -> bool {
         cx.window
             .current_frame
-            .key_dispatcher
-            .as_ref()
-            .unwrap()
+            .dispatch_tree
             .focus_contains(*self, other)
     }
 }
@@ -213,7 +212,7 @@ pub struct Window {
 pub(crate) struct Frame {
     element_states: HashMap<GlobalElementId, AnyBox>,
     mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
-    pub(crate) key_dispatcher: Option<KeyDispatcher>,
+    pub(crate) dispatch_tree: DispatchTree,
     pub(crate) focus_listeners: Vec<AnyFocusListener>,
     pub(crate) scene_builder: SceneBuilder,
     z_index_stack: StackingOrder,
@@ -222,11 +221,11 @@ pub(crate) struct Frame {
 }
 
 impl Frame {
-    pub fn new(key_dispatcher: KeyDispatcher) -> Self {
+    pub fn new(dispatch_tree: DispatchTree) -> Self {
         Frame {
             element_states: HashMap::default(),
             mouse_listeners: HashMap::default(),
-            key_dispatcher: Some(key_dispatcher),
+            dispatch_tree,
             focus_listeners: Vec::new(),
             scene_builder: SceneBuilder::default(),
             z_index_stack: StackingOrder::default(),
@@ -302,8 +301,8 @@ impl Window {
             layout_engine: TaffyLayoutEngine::new(),
             root_view: None,
             element_id_stack: GlobalElementId::default(),
-            previous_frame: Frame::new(KeyDispatcher::new(cx.keymap.clone())),
-            current_frame: Frame::new(KeyDispatcher::new(cx.keymap.clone())),
+            previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone())),
+            current_frame: Frame::new(DispatchTree::new(cx.keymap.clone())),
             focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
             focus_listeners: SubscriberSet::new(),
             default_prevented: true,
@@ -423,9 +422,14 @@ impl<'a> WindowContext<'a> {
     pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
         if let Some(focus_handle) = self.focused() {
             self.defer(move |cx| {
-                let dispatcher = cx.window.current_frame.key_dispatcher.take().unwrap();
-                dispatcher.dispatch_action(focus_handle.id, action, cx);
-                cx.window.current_frame.key_dispatcher = Some(dispatcher);
+                if let Some(node_id) = cx
+                    .window
+                    .current_frame
+                    .dispatch_tree
+                    .focusable_node_id(focus_handle.id)
+                {
+                    cx.dispatch_action_on_node(node_id, action);
+                }
             })
         }
     }
@@ -723,12 +727,14 @@ impl<'a> WindowContext<'a> {
         &mut self,
         handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static,
     ) {
-        let key_dispatcher = self.window.current_frame.key_dispatcher.as_mut().unwrap();
-        key_dispatcher.on_key_event(Box::new(move |event, phase, cx| {
-            if let Some(event) = event.downcast_ref::<Event>() {
-                handler(event, phase, cx)
-            }
-        }));
+        self.window
+            .current_frame
+            .dispatch_tree
+            .on_key_event(Rc::new(move |event, phase, cx| {
+                if let Some(event) = event.downcast_ref::<Event>() {
+                    handler(event, phase, cx)
+                }
+            }));
     }
 
     /// Register an action listener on the window for the current frame. The type of action
@@ -742,10 +748,9 @@ impl<'a> WindowContext<'a> {
         action_type: TypeId,
         handler: impl Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static,
     ) {
-        let key_dispatcher = self.window.current_frame.key_dispatcher.as_mut().unwrap();
-        key_dispatcher.on_action(
+        self.window.current_frame.dispatch_tree.on_action(
             action_type,
-            Box::new(move |action, phase, cx| handler(action, phase, cx)),
+            Rc::new(move |action, phase, cx| handler(action, phase, cx)),
         );
     }
 
@@ -1110,7 +1115,7 @@ impl<'a> WindowContext<'a> {
         frame.element_states.clear();
         frame.mouse_listeners.values_mut().for_each(Vec::clear);
         frame.focus_listeners.clear();
-        frame.key_dispatcher.as_mut().map(KeyDispatcher::clear);
+        frame.dispatch_tree.clear();
     }
 
     /// Dispatch a mouse or keyboard event on the window.
@@ -1172,63 +1177,172 @@ impl<'a> WindowContext<'a> {
         };
 
         if let Some(any_mouse_event) = event.mouse_event() {
-            if let Some(mut handlers) = self
-                .window
-                .current_frame
-                .mouse_listeners
-                .remove(&any_mouse_event.type_id())
-            {
-                // Because handlers may add other handlers, we sort every time.
-                handlers.sort_by(|(a, _), (b, _)| a.cmp(b));
+            self.dispatch_mouse_event(any_mouse_event);
+        } else if let Some(any_key_event) = event.keyboard_event() {
+            self.dispatch_key_event(any_key_event);
+        }
 
-                // Capture phase, events bubble from back to front. Handlers for this phase are used for
-                // special purposes, such as detecting events outside of a given Bounds.
-                for (_, handler) in &mut handlers {
-                    handler(any_mouse_event, DispatchPhase::Capture, self);
+        !self.app.propagate_event
+    }
+
+    fn dispatch_mouse_event(&mut self, event: &dyn Any) {
+        if let Some(mut handlers) = self
+            .window
+            .current_frame
+            .mouse_listeners
+            .remove(&event.type_id())
+        {
+            // Because handlers may add other handlers, we sort every time.
+            handlers.sort_by(|(a, _), (b, _)| a.cmp(b));
+
+            // Capture phase, events bubble from back to front. Handlers for this phase are used for
+            // special purposes, such as detecting events outside of a given Bounds.
+            for (_, handler) in &mut handlers {
+                handler(event, DispatchPhase::Capture, self);
+                if !self.app.propagate_event {
+                    break;
+                }
+            }
+
+            // Bubble phase, where most normal handlers do their work.
+            if self.app.propagate_event {
+                for (_, handler) in handlers.iter_mut().rev() {
+                    handler(event, DispatchPhase::Bubble, self);
                     if !self.app.propagate_event {
                         break;
                     }
                 }
+            }
 
-                // Bubble phase, where most normal handlers do their work.
-                if self.app.propagate_event {
-                    for (_, handler) in handlers.iter_mut().rev() {
-                        handler(any_mouse_event, DispatchPhase::Bubble, self);
-                        if !self.app.propagate_event {
-                            break;
-                        }
+            if self.app.propagate_event && event.downcast_ref::<MouseUpEvent>().is_some() {
+                self.active_drag = None;
+            }
+
+            // Just in case any handlers added new handlers, which is weird, but possible.
+            handlers.extend(
+                self.window
+                    .current_frame
+                    .mouse_listeners
+                    .get_mut(&event.type_id())
+                    .into_iter()
+                    .flat_map(|handlers| handlers.drain(..)),
+            );
+            self.window
+                .current_frame
+                .mouse_listeners
+                .insert(event.type_id(), handlers);
+        }
+    }
+
+    fn dispatch_key_event(&mut self, event: &dyn Any) {
+        if let Some(node_id) = self.window.focus.and_then(|focus_id| {
+            self.window
+                .current_frame
+                .dispatch_tree
+                .focusable_node_id(focus_id)
+        }) {
+            let dispatch_path = self
+                .window
+                .current_frame
+                .dispatch_tree
+                .dispatch_path(node_id);
+
+            // Capture phase
+            let mut context_stack: SmallVec<[KeyContext; 16]> = SmallVec::new();
+            self.propagate_event = true;
+
+            for node_id in &dispatch_path {
+                let node = self.window.current_frame.dispatch_tree.node(*node_id);
+
+                if !node.context.is_empty() {
+                    context_stack.push(node.context.clone());
+                }
+
+                for key_listener in node.key_listeners.clone() {
+                    key_listener(event, DispatchPhase::Capture, self);
+                    if !self.propagate_event {
+                        return;
                     }
                 }
+            }
 
-                if self.app.propagate_event
-                    && any_mouse_event.downcast_ref::<MouseUpEvent>().is_some()
-                {
-                    self.active_drag = None;
+            // Bubble phase
+            for node_id in dispatch_path.iter().rev() {
+                // Handle low level key events
+                let node = self.window.current_frame.dispatch_tree.node(*node_id);
+                for key_listener in node.key_listeners.clone() {
+                    key_listener(event, DispatchPhase::Bubble, self);
+                    if !self.propagate_event {
+                        return;
+                    }
                 }
 
-                // Just in case any handlers added new handlers, which is weird, but possible.
-                handlers.extend(
-                    self.window
-                        .current_frame
-                        .mouse_listeners
-                        .get_mut(&any_mouse_event.type_id())
-                        .into_iter()
-                        .flat_map(|handlers| handlers.drain(..)),
-                );
-                self.window
-                    .current_frame
-                    .mouse_listeners
-                    .insert(any_mouse_event.type_id(), handlers);
+                // Match keystrokes
+                let node = self.window.current_frame.dispatch_tree.node(*node_id);
+                if !node.context.is_empty() {
+                    if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
+                        if let Some(action) = self
+                            .window
+                            .current_frame
+                            .dispatch_tree
+                            .dispatch_key(&key_down_event.keystroke, &context_stack)
+                        {
+                            self.dispatch_action_on_node(*node_id, action);
+                            if !self.propagate_event {
+                                return;
+                            }
+                        }
+                    }
+
+                    context_stack.pop();
+                }
             }
-        } else if let Some(any_key_event) = event.keyboard_event() {
-            if let Some(focus_id) = self.window.focus {
-                let mut dispatcher = self.window.current_frame.key_dispatcher.take().unwrap();
-                dispatcher.dispatch_key(focus_id, any_key_event, self);
-                self.window.current_frame.key_dispatcher = Some(dispatcher);
+        }
+    }
+
+    fn dispatch_action_on_node(&mut self, node_id: DispatchNodeId, action: Box<dyn Action>) {
+        let dispatch_path = self
+            .window
+            .current_frame
+            .dispatch_tree
+            .dispatch_path(node_id);
+
+        // Capture phase
+        for node_id in &dispatch_path {
+            let node = self.window.current_frame.dispatch_tree.node(*node_id);
+            for ActionListener {
+                action_type,
+                listener,
+            } in node.action_listeners.clone()
+            {
+                let any_action = action.as_any();
+                if action_type == any_action.type_id() {
+                    listener(any_action, DispatchPhase::Capture, self);
+                    if !self.propagate_event {
+                        return;
+                    }
+                }
             }
         }
 
-        !self.app.propagate_event
+        // Bubble phase
+        for node_id in dispatch_path.iter().rev() {
+            let node = self.window.current_frame.dispatch_tree.node(*node_id);
+            for ActionListener {
+                action_type,
+                listener,
+            } in node.action_listeners.clone()
+            {
+                let any_action = action.as_any();
+                if action_type == any_action.type_id() {
+                    self.propagate_event = false; // Actions stop propagation by default during the bubble phase
+                    listener(any_action, DispatchPhase::Bubble, self);
+                    if !self.propagate_event {
+                        return;
+                    }
+                }
+            }
+        }
     }
 
     /// Register the given handler to be invoked whenever the global of the given type
@@ -1261,9 +1375,7 @@ impl<'a> WindowContext<'a> {
         if let Some(focus_id) = self.window.focus {
             self.window
                 .current_frame
-                .key_dispatcher
-                .as_ref()
-                .unwrap()
+                .dispatch_tree
                 .available_actions(focus_id)
         } else {
             Vec::new()
@@ -1926,17 +2038,20 @@ impl<'a, V: 'static> ViewContext<'a, V> {
         f: impl FnOnce(Option<FocusHandle>, &mut Self) -> R,
     ) -> R {
         let window = &mut self.window;
-        let old_dispatcher = window.previous_frame.key_dispatcher.as_mut().unwrap();
-        let current_dispatcher = window.current_frame.key_dispatcher.as_mut().unwrap();
 
-        current_dispatcher.push_node(context, old_dispatcher);
+        window
+            .current_frame
+            .dispatch_tree
+            .push_node(context, &mut window.previous_frame.dispatch_tree);
         if let Some(focus_handle) = focus_handle.as_ref() {
-            current_dispatcher.make_focusable(focus_handle.id);
+            window
+                .current_frame
+                .dispatch_tree
+                .make_focusable(focus_handle.id);
         }
         let result = f(focus_handle, self);
 
-        let current_dispatcher = self.window.current_frame.key_dispatcher.as_mut().unwrap();
-        current_dispatcher.pop_node();
+        self.window.current_frame.dispatch_tree.pop_node();
 
         result
     }