Pārlūkot izejas kodu

Merge remote-tracking branch 'upstream/master' into clean_up_rsx_imports

Evan Almloff 3 gadi atpakaļ
vecāks
revīzija
ef4ece42b3

+ 3 - 0
packages/core/src/events.rs

@@ -63,6 +63,9 @@ pub struct UserEvent {
     /// The event type IE "onclick" or "onmouseover"
     pub name: &'static str,
 
+    /// If the event is bubbles up through the vdom
+    pub bubbles: bool,
+
     /// The event data to be passed onto the event handler
     pub data: Arc<dyn Any + Send + Sync>,
 }

+ 5 - 1
packages/core/src/scopes.rs

@@ -335,7 +335,7 @@ impl ScopeArena {
                             log::trace!("calling listener {:?}", listener.event);
                             if state.canceled.get() {
                                 // stop bubbling if canceled
-                                break;
+                                return;
                             }
 
                             let mut cb = listener.callback.borrow_mut();
@@ -349,6 +349,10 @@ impl ScopeArena {
                                     data: event.data.clone(),
                                 });
                             }
+
+                            if !event.bubbles {
+                                return;
+                            }
                         }
                     }
 

+ 3 - 1
packages/desktop/src/events.rs

@@ -4,6 +4,7 @@ use std::any::Any;
 use std::sync::Arc;
 
 use dioxus_core::{ElementId, EventPriority, UserEvent};
+use dioxus_html::event_bubbles;
 use dioxus_html::on::*;
 
 #[derive(serde::Serialize, serde::Deserialize)]
@@ -47,8 +48,8 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
     } = serde_json::from_value(val).unwrap();
 
     let mounted_dom_id = Some(ElementId(mounted_dom_id as usize));
-
     let name = event_name_from_type(&event);
+
     let event = make_synthetic_event(&event, contents);
 
     UserEvent {
@@ -56,6 +57,7 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
         priority: EventPriority::Low,
         scope_id: None,
         element: mounted_dom_id,
+        bubbles: event_bubbles(name),
         data: event,
     }
 }

+ 88 - 0
packages/html/src/events.rs

@@ -1324,3 +1324,91 @@ pub(crate) fn _event_meta(event: &UserEvent) -> (bool, EventPriority) {
         _ => (true, Low),
     }
 }
+
+pub fn event_bubbles(evt: &str) -> bool {
+    match evt {
+        "copy" => true,
+        "cut" => true,
+        "paste" => true,
+        "compositionend" => true,
+        "compositionstart" => true,
+        "compositionupdate" => true,
+        "keydown" => true,
+        "keypress" => true,
+        "keyup" => true,
+        "focus" => false,
+        "focusout" => true,
+        "focusin" => true,
+        "blur" => false,
+        "change" => true,
+        "input" => true,
+        "invalid" => true,
+        "reset" => true,
+        "submit" => true,
+        "click" => true,
+        "contextmenu" => true,
+        "doubleclick" => true,
+        "dblclick" => true,
+        "drag" => true,
+        "dragend" => true,
+        "dragenter" => false,
+        "dragexit" => false,
+        "dragleave" => true,
+        "dragover" => true,
+        "dragstart" => true,
+        "drop" => true,
+        "mousedown" => true,
+        "mouseenter" => false,
+        "mouseleave" => false,
+        "mousemove" => true,
+        "mouseout" => true,
+        "scroll" => false,
+        "mouseover" => true,
+        "mouseup" => true,
+        "pointerdown" => true,
+        "pointermove" => true,
+        "pointerup" => true,
+        "pointercancel" => true,
+        "gotpointercapture" => true,
+        "lostpointercapture" => true,
+        "pointerenter" => false,
+        "pointerleave" => false,
+        "pointerover" => true,
+        "pointerout" => true,
+        "select" => true,
+        "touchcancel" => true,
+        "touchend" => true,
+        "touchmove" => true,
+        "touchstart" => true,
+        "wheel" => true,
+        "abort" => false,
+        "canplay" => true,
+        "canplaythrough" => true,
+        "durationchange" => true,
+        "emptied" => true,
+        "encrypted" => true,
+        "ended" => true,
+        "error" => false,
+        "loadeddata" => true,
+        "loadedmetadata" => true,
+        "loadstart" => false,
+        "pause" => true,
+        "play" => true,
+        "playing" => true,
+        "progress" => false,
+        "ratechange" => true,
+        "seeked" => true,
+        "seeking" => true,
+        "stalled" => true,
+        "suspend" => true,
+        "timeupdate" => true,
+        "volumechange" => true,
+        "waiting" => true,
+        "animationstart" => true,
+        "animationend" => true,
+        "animationiteration" => true,
+        "transitionend" => true,
+        "toggle" => true,
+        _ => panic!("unsupported event type {:?}", evt),
+    }
+}

+ 8 - 2
packages/interpreter/src/bindings.rs

@@ -48,10 +48,16 @@ extern "C" {
     pub fn CreatePlaceholder(this: &Interpreter, root: u64);
 
     #[wasm_bindgen(method)]
-    pub fn NewEventListener(this: &Interpreter, name: &str, root: u64, handler: &Function);
+    pub fn NewEventListener(
+        this: &Interpreter,
+        name: &str,
+        root: u64,
+        handler: &Function,
+        bubbles: bool,
+    );
 
     #[wasm_bindgen(method)]
-    pub fn RemoveEventListener(this: &Interpreter, root: u64, name: &str);
+    pub fn RemoveEventListener(this: &Interpreter, root: u64, name: &str, bubbles: bool);
 
     #[wasm_bindgen(method)]
     pub fn SetText(this: &Interpreter, root: u64, text: JsValue);

+ 239 - 18
packages/interpreter/src/interpreter.js

@@ -5,11 +5,64 @@ export function main() {
     window.ipc.postMessage(serializeIpcMessage("initialize"));
   }
 }
+
+class ListenerMap {
+  constructor(root) {
+    // bubbling events can listen at the root element
+    this.global = {};
+    // non bubbling events listen at the element the listener was created at
+    this.local = {};
+    this.root = root;
+  }
+
+  createBubbling(event_name, handler) {
+    if (this.global[event_name] === undefined) {
+      this.global[event_name] = {};
+      this.global[event_name].active = 1;
+      this.global[event_name].callback = handler;
+      this.root.addEventListener(event_name, handler);
+    } else {
+      this.global[event_name].active++;
+    }
+  }
+
+  createNonBubbling(event_name, element, handler) {
+    const id = element.getAttribute("data-dioxus-id");
+    if (!this.local[id]) {
+      this.local[id] = {};
+    }
+    this.local[id][event_name] = handler;
+    element.addEventListener(event_name, handler);
+  }
+
+  removeBubbling(event_name) {
+    this.global[event_name].active--;
+    if (this.global[event_name].active === 0) {
+      this.root.removeEventListener(event_name, this.global[event_name].callback);
+      delete this.global[event_name];
+    }
+  }
+
+  removeNonBubbling(element, event_name) {
+    const id = element.getAttribute("data-dioxus-id");
+    delete this.local[id][event_name];
+    if (this.local[id].length === 0) {
+      delete this.local[id];
+    }
+    element.removeEventListener(event_name, handler);
+  }
+
+  removeAllNonBubbling(element) {
+    const id = element.getAttribute("data-dioxus-id");
+    delete this.local[id];
+  }
+}
+
 export class Interpreter {
   constructor(root) {
     this.root = root;
     this.stack = [root];
-    this.listeners = {};
+    this.listeners = new ListenerMap(root);
     this.handlers = {};
     this.lastNodeWasText = false;
     this.nodes = [root];
@@ -40,6 +93,7 @@ export class Interpreter {
   ReplaceWith(root_id, m) {
     let root = this.nodes[root_id];
     let els = this.stack.splice(this.stack.length - m);
+    this.listeners.removeAllNonBubbling(root);
     root.replaceWith(...els);
   }
   InsertAfter(root, n) {
@@ -54,6 +108,7 @@ export class Interpreter {
   }
   Remove(root) {
     let node = this.nodes[root];
+    this.listeners.removeAllNonBubbling(node);
     if (node !== undefined) {
       node.remove();
     }
@@ -79,25 +134,24 @@ export class Interpreter {
     this.stack.push(el);
     this.nodes[root] = el;
   }
-  NewEventListener(event_name, root, handler) {
+  NewEventListener(event_name, root, handler, bubbles) {
     const element = this.nodes[root];
     element.setAttribute("data-dioxus-id", `${root}`);
-    if (this.listeners[event_name] === undefined) {
-      this.listeners[event_name] = 1;
-      this.handlers[event_name] = handler;
-      this.root.addEventListener(event_name, handler);
-    } else {
-      this.listeners[event_name]++;
+    if (bubbles) {
+      this.listeners.createBubbling(event_name, handler);
+    }
+    else {
+      this.listeners.createNonBubbling(event_name, element, handler);
     }
   }
-  RemoveEventListener(root, event_name) {
+  RemoveEventListener(root, event_name, bubbles) {
     const element = this.nodes[root];
     element.removeAttribute(`data-dioxus-id`);
-    this.listeners[event_name]--;
-    if (this.listeners[event_name] === 0) {
-      this.root.removeEventListener(event_name, this.handlers[event_name]);
-      delete this.listeners[event_name];
-      delete this.handlers[event_name];
+    if (bubbles) {
+      this.listeners.removeBubbling(event_name)
+    }
+    else {
+      this.listeners.removeNonBubbling(element, event_name);
     }
   }
   SetText(root, text) {
@@ -198,12 +252,9 @@ export class Interpreter {
         this.RemoveEventListener(edit.root, edit.event_name);
         break;
       case "NewEventListener":
-        console.log(this.listeners);
-
         // this handler is only provided on desktop implementations since this
         // method is not used by the web implementation
         let handler = (event) => {
-          console.log(event);
 
           let target = event.target;
           if (target != null) {
@@ -292,7 +343,8 @@ export class Interpreter {
             );
           }
         };
-        this.NewEventListener(edit.event_name, edit.root, handler);
+        this.NewEventListener(edit.event_name, edit.root, handler, event_bubbles(edit.event_name));
+
         break;
       case "SetText":
         this.SetText(edit.root, edit.text);
@@ -607,3 +659,172 @@ const bool_attrs = {
   selected: true,
   truespeed: true,
 };
+
+function event_bubbles(event) {
+  switch (event) {
+    case "copy":
+      return true;
+    case "cut":
+      return true;
+    case "paste":
+      return true;
+    case "compositionend":
+      return true;
+    case "compositionstart":
+      return true;
+    case "compositionupdate":
+      return true;
+    case "keydown":
+      return true;
+    case "keypress":
+      return true;
+    case "keyup":
+      return true;
+    case "focus":
+      return false;
+    case "focusout":
+      return true;
+    case "focusin":
+      return true;
+    case "blur":
+      return false;
+    case "change":
+      return true;
+    case "input":
+      return true;
+    case "invalid":
+      return true;
+    case "reset":
+      return true;
+    case "submit":
+      return true;
+    case "click":
+      return true;
+    case "contextmenu":
+      return true;
+    case "doubleclick":
+      return true;
+    case "dblclick":
+      return true;
+    case "drag":
+      return true;
+    case "dragend":
+      return true;
+    case "dragenter":
+      return false;
+    case "dragexit":
+      return false;
+    case "dragleave":
+      return true;
+    case "dragover":
+      return true;
+    case "dragstart":
+      return true;
+    case "drop":
+      return true;
+    case "mousedown":
+      return true;
+    case "mouseenter":
+      return false;
+    case "mouseleave":
+      return false;
+    case "mousemove":
+      return true;
+    case "mouseout":
+      return true;
+    case "scroll":
+      return false;
+    case "mouseover":
+      return true;
+    case "mouseup":
+      return true;
+    case "pointerdown":
+      return true;
+    case "pointermove":
+      return true;
+    case "pointerup":
+      return true;
+    case "pointercancel":
+      return true;
+    case "gotpointercapture":
+      return true;
+    case "lostpointercapture":
+      return true;
+    case "pointerenter":
+      return false;
+    case "pointerleave":
+      return false;
+    case "pointerover":
+      return true;
+    case "pointerout":
+      return true;
+    case "select":
+      return true;
+    case "touchcancel":
+      return true;
+    case "touchend":
+      return true;
+    case "touchmove":
+      return true;
+    case "touchstart":
+      return true;
+    case "wheel":
+      return true;
+    case "abort":
+      return false;
+    case "canplay":
+      return true;
+    case "canplaythrough":
+      return true;
+    case "durationchange":
+      return true;
+    case "emptied":
+      return true;
+    case "encrypted":
+      return true;
+    case "ended":
+      return true;
+    case "error":
+      return false;
+    case "loadeddata":
+      return true;
+    case "loadedmetadata":
+      return true;
+    case "loadstart":
+      return false;
+    case "pause":
+      return true;
+    case "play":
+      return true;
+    case "playing":
+      return true;
+    case "progress":
+      return false;
+    case "ratechange":
+      return true;
+    case "seeked":
+      return true;
+    case "seeking":
+      return true;
+    case "stalled":
+      return true;
+    case "suspend":
+      return true;
+    case "timeupdate":
+      return true;
+    case "volumechange":
+      return true;
+    case "waiting":
+      return true;
+    case "animationstart":
+      return true;
+    case "animationend":
+      return true;
+    case "animationiteration":
+      return true;
+    case "transitionend":
+      return true;
+    case "toggle":
+      return true;
+  }
+}

+ 2 - 0
packages/liveview/src/events.rs

@@ -6,6 +6,7 @@ use std::any::Any;
 use std::sync::Arc;
 
 use dioxus_core::{ElementId, EventPriority, UserEvent};
+use dioxus_html::event_bubbles;
 use dioxus_html::on::*;
 
 #[derive(serde::Serialize, serde::Deserialize)]
@@ -46,6 +47,7 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
         scope_id: None,
         element: mounted_dom_id,
         data: event,
+        bubbles: event_bubbles(name),
     }
 }
 

+ 6 - 1
packages/tui/src/hooks.rs

@@ -9,7 +9,7 @@ use dioxus_html::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, S
 use dioxus_html::input_data::keyboard_types::Modifiers;
 use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons;
 use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet};
-use dioxus_html::{on::*, KeyCode};
+use dioxus_html::{event_bubbles, on::*, KeyCode};
 use std::{
     any::Any,
     cell::{RefCell, RefMut},
@@ -187,6 +187,7 @@ impl InnerInputState {
                     name: "focus",
                     element: Some(id),
                     data: Arc::new(FocusData {}),
+                    bubbles: event_bubbles("focus"),
                 });
                 resolved_events.push(UserEvent {
                     scope_id: None,
@@ -194,6 +195,7 @@ impl InnerInputState {
                     name: "focusin",
                     element: Some(id),
                     data: Arc::new(FocusData {}),
+                    bubbles: event_bubbles("focusin"),
                 });
             }
             if let Some(id) = old_focus {
@@ -203,6 +205,7 @@ impl InnerInputState {
                     name: "focusout",
                     element: Some(id),
                     data: Arc::new(FocusData {}),
+                    bubbles: event_bubbles("focusout"),
                 });
             }
         }
@@ -248,6 +251,7 @@ impl InnerInputState {
                     name,
                     element: Some(node.id),
                     data,
+                    bubbles: event_bubbles(name),
                 })
             }
         }
@@ -649,6 +653,7 @@ impl RinkInputHandler {
                             name: event,
                             element: Some(node.id),
                             data: data.clone(),
+                            bubbles: event_bubbles(event),
                         });
                     }
                 }

+ 39 - 11
packages/tui/tests/events.rs

@@ -1,8 +1,31 @@
-use std::time::Duration;
-
 use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent};
 use dioxus::prelude::*;
 use dioxus_tui::TuiContext;
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+/// The tui renderer will look for any event that has occured or any future that has resolved in a loop.
+/// It will resolve at most one event per loop.
+/// This future will resolve after a certain number of polls. If the number of polls is greater than the number of events triggered, and the event has not been recieved there is an issue with the event system.
+struct PollN(usize);
+impl PollN {
+    fn new(n: usize) -> Self {
+        PollN(n)
+    }
+}
+impl Future for PollN {
+    type Output = ();
+
+    fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
+        if self.0 == 0 {
+            Poll::Ready(())
+        } else {
+            self.0 -= 1;
+            Poll::Pending
+        }
+    }
+}
 
 #[test]
 fn key_down() {
@@ -13,12 +36,17 @@ fn key_down() {
         let tui_ctx: TuiContext = cx.consume_context().unwrap();
         let render_count_handle = render_count.clone();
         cx.spawn(async move {
-            tokio::time::sleep(Duration::from_millis(100)).await;
+            PollN::new(3).await;
             render_count_handle.modify(|x| *x + 1);
         });
         if *render_count.get() > 2 {
             panic!("Event was not received");
         }
+        // focus the element
+        tui_ctx.inject_event(Event::Key(KeyEvent {
+            code: KeyCode::Tab,
+            modifiers: KeyModifiers::NONE,
+        }));
         tui_ctx.inject_event(Event::Key(KeyEvent {
             code: KeyCode::Char('a'),
             modifiers: KeyModifiers::NONE,
@@ -45,7 +73,7 @@ fn mouse_down() {
         let tui_ctx: TuiContext = cx.consume_context().unwrap();
         let render_count_handle = render_count.clone();
         cx.spawn(async move {
-            tokio::time::sleep(Duration::from_millis(100)).await;
+            PollN::new(2).await;
             render_count_handle.modify(|x| *x + 1);
         });
         if *render_count.get() > 2 {
@@ -79,7 +107,7 @@ fn mouse_up() {
         let tui_ctx: TuiContext = cx.consume_context().unwrap();
         let render_count_handle = render_count.clone();
         cx.spawn(async move {
-            tokio::time::sleep(Duration::from_millis(100)).await;
+            PollN::new(3).await;
             render_count_handle.modify(|x| *x + 1);
         });
         if *render_count.get() > 2 {
@@ -118,7 +146,7 @@ fn mouse_enter() {
         let tui_ctx: TuiContext = cx.consume_context().unwrap();
         let render_count_handle = render_count.clone();
         cx.spawn(async move {
-            tokio::time::sleep(Duration::from_millis(100)).await;
+            PollN::new(3).await;
             render_count_handle.modify(|x| *x + 1);
         });
         if *render_count.get() > 2 {
@@ -157,7 +185,7 @@ fn mouse_exit() {
         let tui_ctx: TuiContext = cx.consume_context().unwrap();
         let render_count_handle = render_count.clone();
         cx.spawn(async move {
-            tokio::time::sleep(Duration::from_millis(100)).await;
+            PollN::new(3).await;
             render_count_handle.modify(|x| *x + 1);
         });
         if *render_count.get() > 2 {
@@ -196,7 +224,7 @@ fn mouse_move() {
         let tui_ctx: TuiContext = cx.consume_context().unwrap();
         let render_count_handle = render_count.clone();
         cx.spawn(async move {
-            tokio::time::sleep(Duration::from_millis(100)).await;
+            PollN::new(3).await;
             render_count_handle.modify(|x| *x + 1);
         });
         if *render_count.get() > 2 {
@@ -235,7 +263,7 @@ fn wheel() {
         let tui_ctx: TuiContext = cx.consume_context().unwrap();
         let render_count_handle = render_count.clone();
         cx.spawn(async move {
-            tokio::time::sleep(Duration::from_millis(100)).await;
+            PollN::new(3).await;
             render_count_handle.modify(|x| *x + 1);
         });
         if *render_count.get() > 2 {
@@ -275,7 +303,7 @@ fn click() {
         let tui_ctx: TuiContext = cx.consume_context().unwrap();
         let render_count_handle = render_count.clone();
         cx.spawn(async move {
-            tokio::time::sleep(Duration::from_millis(100)).await;
+            PollN::new(3).await;
             render_count_handle.modify(|x| *x + 1);
         });
         if *render_count.get() > 2 {
@@ -314,7 +342,7 @@ fn context_menu() {
         let tui_ctx: TuiContext = cx.consume_context().unwrap();
         let render_count_handle = render_count.clone();
         cx.spawn(async move {
-            tokio::time::sleep(Duration::from_millis(100)).await;
+            PollN::new(3).await;
             render_count_handle.modify(|x| *x + 1);
         });
         if *render_count.get() > 2 {

+ 12 - 4
packages/web/src/dom.rs

@@ -8,6 +8,7 @@
 //! - Partial delegation?>
 
 use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent};
+use dioxus_html::event_bubbles;
 use dioxus_interpreter_js::Interpreter;
 use js_sys::Function;
 use std::{any::Any, rc::Rc, sync::Arc};
@@ -45,6 +46,7 @@ impl WebsysDom {
                             element: Some(ElementId(id)),
                             scope_id: None,
                             priority: dioxus_core::EventPriority::Medium,
+                            bubbles: event.bubbles(),
                         });
                     }
                     Some(Err(e)) => {
@@ -64,6 +66,7 @@ impl WebsysDom {
                                 element: None,
                                 scope_id: None,
                                 priority: dioxus_core::EventPriority::Low,
+                                bubbles: event.bubbles(),
                             });
                         }
                     }
@@ -121,12 +124,17 @@ impl WebsysDom {
                     event_name, root, ..
                 } => {
                     let handler: &Function = self.handler.as_ref().unchecked_ref();
-                    self.interpreter.NewEventListener(event_name, root, handler);
+                    self.interpreter.NewEventListener(
+                        event_name,
+                        root,
+                        handler,
+                        event_bubbles(event_name),
+                    );
                 }
 
-                DomEdit::RemoveEventListener { root, event } => {
-                    self.interpreter.RemoveEventListener(root, event)
-                }
+                DomEdit::RemoveEventListener { root, event } => self
+                    .interpreter
+                    .RemoveEventListener(root, event, event_bubbles(event)),
 
                 DomEdit::RemoveAttribute { root, name, ns } => {
                     self.interpreter.RemoveAttribute(root, name, ns)

+ 2 - 0
packages/web/src/rehydrate.rs

@@ -1,5 +1,6 @@
 use crate::dom::WebsysDom;
 use dioxus_core::{VNode, VirtualDom};
+use dioxus_html::event_bubbles;
 use wasm_bindgen::JsCast;
 use web_sys::{Comment, Element, Node, Text};
 
@@ -111,6 +112,7 @@ impl WebsysDom {
                         listener.event,
                         listener.mounted_node.get().unwrap().as_u64(),
                         self.handler.as_ref().unchecked_ref(),
+                        event_bubbles(listener.event),
                     );
                 }