Pārlūkot izejas kodu

feat: get web working properly

Jonathan Kelley 2 gadi atpakaļ
vecāks
revīzija
18d6b1ad6f

+ 3 - 3
Cargo.toml

@@ -12,13 +12,13 @@ members = [
     "packages/desktop",
     "packages/mobile",
     "packages/interpreter",
+    # "packages/tui",
     "packages/fermi",
-    "packages/tui",
     "packages/liveview",
     "packages/autofmt",
     "packages/rsx",
-    "packages/native-core",
-    "packages/native-core-macro",
+    # "packages/native-core",
+    # "packages/native-core-macro",
     # "packages/edit-stream",
     "docs/guide",
 ]

+ 32 - 0
examples/simple_todo.rs

@@ -0,0 +1,32 @@
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let mut idx = use_state(cx, || 0);
+    let onhover = |h| println!("go!");
+
+    cx.render(rsx! {
+        div {
+            button { onclick: move |_| idx += 1, "+" }
+            button { onclick: move |_| idx -= 1, "-" }
+            ul {
+                (0..**idx).map(|i| rsx! {
+                    Child { i: i, onhover: onhover }
+                })
+            }
+        }
+    })
+}
+
+#[inline_props]
+fn Child<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element {
+    cx.render(rsx! {
+        li {
+            onmouseover: move |e| onhover.call(e),
+            "{i}"
+        }
+    })
+}

+ 6 - 1
examples/todomvc.rs

@@ -57,7 +57,10 @@ pub fn app(cx: Scope<()>) -> Element {
                         placeholder: "What needs to be done?",
                         value: "{draft}",
                         autofocus: "true",
-                        oninput: move |evt| draft.set(evt.value.clone()),
+                        oninput: move |evt| {
+                            println!("calling oninput");
+                            draft.set(evt.value.clone());
+                        },
                         onkeydown: move |evt| {
                             if evt.key() == Key::Enter && !draft.is_empty() {
                                 todos.make_mut().insert(
@@ -121,6 +124,8 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
     let completed = if todo.checked { "completed" } else { "" };
     let editing = if **is_editing { "editing" } else { "" };
 
+    println!("rendering todo entry");
+
     cx.render(rsx!{
         li {
             class: "{completed} {editing}",

+ 19 - 13
packages/core/src/arena.rs

@@ -13,7 +13,7 @@ pub(crate) struct ElementRef {
     pub path: ElementPath,
 
     // The actual template
-    pub template: *mut VNode<'static>,
+    pub template: *const VNode<'static>,
 }
 
 #[derive(Clone, Copy)]
@@ -58,10 +58,22 @@ impl VirtualDom {
     }
 
     pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<ElementRef> {
-        assert_ne!(el, ElementId(0));
+        if el.0 == 0 {
+            panic!(
+                "Invalid element set to 0 - {:#?}",
+                std::backtrace::Backtrace::force_capture()
+            )
+        }
+
+        println!("reclaiming {:?}", el);
         self.elements.try_remove(el.0)
     }
 
+    pub(crate) fn update_template(&mut self, el: ElementId, node: &VNode) {
+        let node: *const VNode = node as *const _;
+        self.elements[el.0].template = unsafe { std::mem::transmute(node) };
+    }
+
     // Drop a scope and all its children
     pub(crate) fn drop_scope(&mut self, id: ScopeId) {
         let scope = self.scopes.get(id.0).unwrap();
@@ -73,21 +85,15 @@ impl VirtualDom {
             }
         }
 
-        let scope = self.scopes.get(id.0).unwrap();
-
-        if let Some(root) = unsafe { scope.as_ref().previous_frame().try_load_node() } {
-            let root = unsafe { root.extend_lifetime_ref() };
-            if let RenderReturn::Sync(Ok(node)) = root {
-                self.drop_scope_inner(node)
-            }
-        }
-
-        let scope = self.scopes.get(id.0).unwrap().as_ref();
+        let scope = self.scopes.get_mut(id.0).unwrap();
+        scope.props.take();
 
         // Drop all the hooks once the children are dropped
         // this means we'll drop hooks bottom-up
-        for hook in scope.hook_list.borrow_mut().drain(..) {
+        for hook in scope.hook_list.get_mut().drain(..) {
+            println!("dropping hook !");
             drop(unsafe { BumpBox::from_raw(hook) });
+            println!("hook dropped !");
         }
     }
 

+ 1 - 1
packages/core/src/create.rs

@@ -129,7 +129,7 @@ impl<'b> VirtualDom {
                                 AttributeValue::Listener(_) => {
                                     self.mutations.push(NewEventListener {
                                         // all listeners start with "on"
-                                        event_name: &unbounded_name[2..],
+                                        name: &unbounded_name[2..],
                                         scope: cur_scope,
                                         id,
                                     })

+ 62 - 8
packages/core/src/diff.rs

@@ -1,3 +1,5 @@
+use std::cell::Cell;
+
 use crate::{
     arena::ElementId,
     factory::RenderReturn,
@@ -59,10 +61,17 @@ impl<'b> VirtualDom {
     }
 
     fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
+        println!(
+            "diffing node {:#?},\n\n{:#?}",
+            left_template.template.id, right_template.template.id
+        );
+
         if left_template.template.id != right_template.template.id {
             return self.light_diff_templates(left_template, right_template);
         }
 
+        println!("diffing attributs...");
+
         for (left_attr, right_attr) in left_template
             .dynamic_attrs
             .iter()
@@ -73,6 +82,11 @@ impl<'b> VirtualDom {
                 .mounted_element
                 .set(left_attr.mounted_element.get());
 
+            // We want to make sure anything listener that gets pulled is valid
+            if let AttributeValue::Listener(_) = right_attr.value {
+                self.update_template(left_attr.mounted_element.get(), right_template);
+            }
+
             if left_attr.value != right_attr.value || left_attr.volatile {
                 // todo: add more types of attribute values
                 match right_attr.value {
@@ -92,6 +106,8 @@ impl<'b> VirtualDom {
             }
         }
 
+        println!("diffing dyn nodes...");
+
         for (idx, (left_node, right_node)) in left_template
             .dynamic_nodes
             .iter()
@@ -101,13 +117,24 @@ impl<'b> VirtualDom {
             match (left_node, right_node) {
                 (Text(left), Text(right)) => self.diff_vtext(left, right),
                 (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right),
+                (Placeholder(left), Placeholder(right)) => {
+                    right.set(left.get());
+                }
                 (Component(left), Component(right)) => {
                     self.diff_vcomponent(left, right, right_template, idx)
                 }
-                _ => self.replace(left_template, right_template),
+                (Placeholder(left), Fragment(right)) => {
+                    self.replace_placeholder_with_nodes(left, right)
+                }
+                (Fragment(left), Placeholder(right)) => {
+                    self.replace_nodes_with_placeholder(left, right)
+                }
+                _ => todo!(),
             };
         }
 
+        println!("applying roots...");
+
         // Make sure the roots get transferred over
         for (left, right) in left_template
             .root_ids
@@ -118,6 +145,30 @@ impl<'b> VirtualDom {
         }
     }
 
+    fn replace_placeholder_with_nodes(&mut self, l: &'b Cell<ElementId>, r: &'b [VNode<'b>]) {
+        let m = self.create_children(r);
+        let id = l.get();
+        self.mutations.push(Mutation::ReplaceWith { id, m });
+        self.reclaim(id);
+    }
+
+    fn replace_nodes_with_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b Cell<ElementId>) {
+        // Create the placeholder first, ensuring we get a dedicated ID for the placeholder
+        let placeholder = self.next_element(&l[0], &[]);
+        r.set(placeholder);
+        self.mutations
+            .push(Mutation::CreatePlaceholder { id: placeholder });
+
+        // Remove the old nodes, except for onea
+        let first = self.replace_inner(&l[0]);
+        self.remove_nodes(&l[1..]);
+
+        self.mutations
+            .push(Mutation::ReplaceWith { id: first, m: 1 });
+
+        self.try_reclaim(first);
+    }
+
     fn diff_vcomponent(
         &mut self,
         left: &'b VComponent<'b>,
@@ -140,6 +191,7 @@ impl<'b> VirtualDom {
             };
             self.mutations
                 .push(Mutation::ReplaceWith { id, m: created });
+            self.drop_scope(left.scope.get().unwrap());
             return;
         }
 
@@ -154,15 +206,12 @@ impl<'b> VirtualDom {
         // If the props are static, then we try to memoize by setting the new with the old
         // The target scopestate still has the reference to the old props, so there's no need to update anything
         // This also implicitly drops the new props since they're not used
-        if left.static_props && unsafe { old.memoize(new.as_ref()) } {
+        if left.static_props && unsafe { old.as_ref().unwrap().memoize(new.as_ref()) } {
             return;
         }
 
-        // If the props are dynamic *or* the memoization failed, then we need to diff the props
-
         // First, move over the props from the old to the new, dropping old props in the process
-        self.scopes[scope_id.0].props = unsafe { std::mem::transmute(new.as_ref()) };
-        right.props.set(Some(new));
+        self.scopes[scope_id.0].props = unsafe { std::mem::transmute(new) };
 
         // Now run the component and diff it
         self.run_scope(scope_id);
@@ -335,7 +384,7 @@ impl<'b> VirtualDom {
         };
     }
 
-    fn diff_non_empty_fragment(&mut self, new: &'b [VNode<'b>], old: &'b [VNode<'b>]) {
+    fn diff_non_empty_fragment(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
         let new_is_keyed = new[0].key.is_some();
         let old_is_keyed = old[0].key.is_some();
         debug_assert!(
@@ -346,6 +395,9 @@ impl<'b> VirtualDom {
             old.iter().all(|o| o.key.is_some() == old_is_keyed),
             "all siblings must be keyed or all siblings must be non-keyed"
         );
+
+        println!("Diffing fragment {:?}, {:?}", old.len(), new.len());
+
         if new_is_keyed && old_is_keyed {
             self.diff_keyed_children(old, new);
         } else {
@@ -368,7 +420,9 @@ impl<'b> VirtualDom {
         debug_assert!(!new.is_empty());
         debug_assert!(!old.is_empty());
 
-        match old.len().cmp(&new.len()) {
+        println!("Diffing non keyed children");
+
+        match dbg!(old.len().cmp(&new.len())) {
             Ordering::Greater => self.remove_nodes(&old[new.len()..]),
             Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
             Ordering::Equal => {}

+ 4 - 4
packages/core/src/mutations.rs

@@ -158,7 +158,7 @@ pub enum Mutation<'a> {
     /// Create a new Event Listener.
     NewEventListener {
         /// The name of the event to listen for.
-        event_name: &'a str,
+        name: &'a str,
 
         /// The ID of the node to attach the listener to.
         scope: ScopeId,
@@ -169,11 +169,11 @@ pub enum Mutation<'a> {
 
     /// Remove an existing Event Listener.
     RemoveEventListener {
+        /// The name of the event to remove.
+        name: &'a str,
+
         /// The ID of the node to remove.
         id: ElementId,
-
-        /// The name of the event to remove.
-        event: &'a str,
     },
     Remove {
         id: ElementId,

+ 5 - 2
packages/core/src/scope_arena.rs

@@ -28,7 +28,7 @@ impl VirtualDom {
             parent,
             id,
             height,
-            props,
+            props: Some(props),
             placeholder: Default::default(),
             node_arena_1: BumpFrame::new(50),
             node_arena_2: BumpFrame::new(50),
@@ -86,6 +86,8 @@ impl VirtualDom {
     }
 
     pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
+        println!("Running scope {:?}", scope_id);
+
         // Cycle to the next frame and then reset it
         // This breaks any latent references, invalidating every pointer referencing into it.
         // Remove all the outdated listeners
@@ -100,7 +102,8 @@ impl VirtualDom {
             scope.hook_idx.set(0);
 
             // safety: due to how we traverse the tree, we know that the scope is not currently aliased
-            let props: &dyn AnyProps = mem::transmute(&*scope.props);
+            let props = scope.props.as_ref().unwrap().as_ref();
+            let props: &dyn AnyProps = mem::transmute(props);
             props.render(scope).extend_lifetime()
         };
 

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

@@ -82,7 +82,7 @@ pub struct ScopeState {
     pub(crate) tasks: Rc<Scheduler>,
     pub(crate) spawned_tasks: HashSet<TaskId>,
 
-    pub(crate) props: Box<dyn AnyProps<'static>>,
+    pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
     pub(crate) placeholder: Cell<Option<ElementId>>,
 }
 

+ 24 - 6
packages/core/src/virtual_dom.rs

@@ -362,6 +362,8 @@ impl VirtualDom {
             let target_path = el_ref.path;
 
             for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
+                println!("{:?} \n {:?} \n {:?}", attr, name, element);
+
                 let this_path = template.template.attr_paths[idx];
 
                 // listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
@@ -383,6 +385,8 @@ impl VirtualDom {
                 }
             }
 
+            println!("calling listeners: {:?}", listeners);
+
             // Now that we've accumulated all the parent attributes for the target element, call them in reverse order
             // We check the bubble state between each call to see if the event has been stopped from bubbling
             for listener in listeners.drain(..).rev() {
@@ -399,6 +403,8 @@ impl VirtualDom {
 
             parent_path = template.parent.and_then(|id| self.elements.get(id.0));
         }
+
+        println!("all listeners exhausted");
     }
 
     /// Wait for the scheduler to have any work.
@@ -448,12 +454,15 @@ impl VirtualDom {
         }
     }
 
-    /// Swap the current mutations with a new
-    fn finalize(&mut self) -> Mutations {
-        // todo: make this a routine
-        let mut out = Mutations::default();
-        std::mem::swap(&mut self.mutations, &mut out);
-        out
+    /// Process all events in the queue until there are no more left
+    pub fn process_events(&mut self) {
+        while let Ok(Some(msg)) = self.rx.try_next() {
+            match msg {
+                SchedulerMsg::Immediate(id) => self.mark_dirty(id),
+                SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task),
+                SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id),
+            }
+        }
     }
 
     /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
@@ -515,6 +524,7 @@ impl VirtualDom {
     ///
     /// If no suspense trees are present
     pub async fn render_with_deadline(&mut self, deadline: impl Future<Output = ()>) -> Mutations {
+        println!("render with deadline");
         pin_mut!(deadline);
 
         loop {
@@ -600,6 +610,14 @@ impl VirtualDom {
             }
         }
     }
+
+    /// Swap the current mutations with a new
+    fn finalize(&mut self) -> Mutations {
+        // todo: make this a routine
+        let mut out = Mutations::default();
+        std::mem::swap(&mut self.mutations, &mut out);
+        out
+    }
 }
 
 impl Drop for VirtualDom {

+ 1 - 1
packages/core/tests/kitchen_sink.rs

@@ -70,7 +70,7 @@ fn dual_stream() {
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
             SetAttribute { name: "class", value: "123", id: ElementId(1), ns: None },
-            NewEventListener { event_name: "click", scope: ScopeId(0), id: ElementId(1) },
+            NewEventListener { name: "click", scope: ScopeId(0), id: ElementId(1) },
             HydrateText { path: &[0, 0], value: "123", id: ElementId(2) },
             AppendChildren { m: 1 }
         ],

+ 49 - 0
packages/core/tests/miri_full_app.rs

@@ -0,0 +1,49 @@
+use dioxus::prelude::*;
+use dioxus_core::ElementId;
+use std::rc::Rc;
+
+#[test]
+fn miri_rollover() {
+    let mut dom = VirtualDom::new(app);
+
+    _ = dom.rebuild();
+
+    for x in 0..3 {
+        dom.handle_event("click", Rc::new(MouseData::default()), ElementId(2), true);
+        dom.process_events();
+        dom.render_immediate();
+    }
+}
+
+fn app(cx: Scope) -> Element {
+    let mut idx = use_state(cx, || 0);
+    let onhover = |h| println!("go!");
+
+    cx.render(rsx! {
+        div {
+            button {
+                onclick: move |_| {
+                    idx += 1;
+                    println!("Clicked");
+                },
+                "+"
+            }
+            button { onclick: move |_| idx -= 1, "-" }
+            ul {
+                (0..**idx).map(|i| rsx! {
+                    Child { i: i, onhover: onhover }
+                })
+            }
+        }
+    })
+}
+
+#[inline_props]
+fn Child<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element {
+    cx.render(rsx! {
+        li {
+            onmouseover: move |e| onhover.call(e),
+            "{i}"
+        }
+    })
+}

+ 5 - 3
packages/desktop/src/controller.rs

@@ -56,7 +56,7 @@ impl DesktopController {
                     let mut queue = edit_queue.lock().unwrap();
                     queue.push(serde_json::to_string(&edits.template_mutations).unwrap());
                     queue.push(serde_json::to_string(&edits.edits).unwrap());
-                    proxy.send_event(UserWindowEvent::Update).unwrap();
+                    proxy.send_event(UserWindowEvent::EditsReady).unwrap();
                 }
 
                 loop {
@@ -67,7 +67,7 @@ impl DesktopController {
                                 let name = value.event.clone();
                                 let el_id = ElementId(value.mounted_dom_id);
                                 if let Some(evt) = decode_event(value) {
-                                    dom.handle_event(&name,  evt, el_id, true);
+                                    dom.handle_event(&name,  evt, el_id,  dioxus_html::events::event_bubbles(&name));
                                 }
                             }
                         }
@@ -81,7 +81,7 @@ impl DesktopController {
                         let mut queue = edit_queue.lock().unwrap();
                         queue.push(serde_json::to_string(&muts.template_mutations).unwrap());
                         queue.push(serde_json::to_string(&muts.edits).unwrap());
-                        let _ = proxy.send_event(UserWindowEvent::Update);
+                        let _ = proxy.send_event(UserWindowEvent::EditsReady);
                     }
                 }
             })
@@ -116,6 +116,8 @@ impl DesktopController {
 
             let (_id, view) = self.webviews.iter_mut().next().unwrap();
 
+            println!("processing pending edits {:?}", new_queue.len());
+
             for edit in new_queue.drain(..) {
                 view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
                     .unwrap();

+ 3 - 2
packages/desktop/src/desktop_context.rs

@@ -161,7 +161,8 @@ impl DesktopContext {
 
 #[derive(Debug)]
 pub enum UserWindowEvent {
-    Update,
+    EditsReady,
+    Initialize,
 
     CloseWindow,
     DragWindow,
@@ -213,7 +214,7 @@ pub(super) fn handler(
     println!("user_event: {:?}", user_event);
 
     match user_event {
-        Update => desktop.try_load_ready_webviews(),
+        Initialize | EditsReady => desktop.try_load_ready_webviews(),
         CloseWindow => *control_flow = ControlFlow::Exit,
         DragWindow => {
             // if the drag_window has any errors, we don't do anything

+ 1 - 1
packages/desktop/src/lib.rs

@@ -154,7 +154,7 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
                                 "initialize" => {
                                     is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
                                     println!("initializing...");
-                                    let _ = proxy.send_event(UserWindowEvent::Update);
+                                    let _ = proxy.send_event(UserWindowEvent::EditsReady);
                                 }
                                 "browser_open" => {
                                     let data = message.params();

+ 87 - 87
packages/html/src/events.rs

@@ -60,90 +60,90 @@ pub use touch::*;
 pub use transition::*;
 pub use wheel::*;
 
-// 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" => false,
-//         "canplaythrough" => false,
-//         "durationchange" => false,
-//         "emptied" => false,
-//         "encrypted" => true,
-//         "ended" => false,
-//         "error" => false,
-//         "loadeddata" => false,
-//         "loadedmetadata" => false,
-//         "loadstart" => false,
-//         "pause" => false,
-//         "play" => false,
-//         "playing" => false,
-//         "progress" => false,
-//         "ratechange" => false,
-//         "seeked" => false,
-//         "seeking" => false,
-//         "stalled" => false,
-//         "suspend" => false,
-//         "timeupdate" => false,
-//         "volumechange" => false,
-//         "waiting" => false,
-//         "animationstart" => true,
-//         "animationend" => true,
-//         "animationiteration" => true,
-//         "transitionend" => true,
-//         "toggle" => true,
-//         _ => panic!("unsupported event type {:?}", evt),
-//     }
-// }
+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" => false,
+        "canplaythrough" => false,
+        "durationchange" => false,
+        "emptied" => false,
+        "encrypted" => true,
+        "ended" => false,
+        "error" => false,
+        "loadeddata" => false,
+        "loadedmetadata" => false,
+        "loadstart" => false,
+        "pause" => false,
+        "play" => false,
+        "playing" => false,
+        "progress" => false,
+        "ratechange" => false,
+        "seeked" => false,
+        "seeking" => false,
+        "stalled" => false,
+        "suspend" => false,
+        "timeupdate" => false,
+        "volumechange" => false,
+        "waiting" => false,
+        "animationstart" => true,
+        "animationend" => true,
+        "animationiteration" => true,
+        "transitionend" => true,
+        "toggle" => true,
+        _ => true,
+    }
+}

+ 2 - 2
packages/html/src/lib.rs

@@ -18,8 +18,8 @@ pub mod events;
 pub mod geometry;
 mod global_attributes;
 pub mod input_data;
-// #[cfg(feature = "wasm-bind")]
-// mod web_sys_bind;
+#[cfg(feature = "wasm-bind")]
+mod web_sys_bind;
 
 pub use elements::*;
 pub use events::*;

+ 3 - 3
packages/html/src/web_sys_bind/events.rs

@@ -1,9 +1,9 @@
-use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
-use crate::input_data::{decode_key_location, decode_mouse_button_set, MouseButton};
-use crate::on::{
+use crate::events::{
     AnimationData, CompositionData, KeyboardData, MouseData, PointerData, TouchData,
     TransitionData, WheelData,
 };
+use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
+use crate::input_data::{decode_key_location, decode_mouse_button_set, MouseButton};
 use keyboard_types::{Code, Key, Modifiers};
 use std::convert::TryInto;
 use std::str::FromStr;

+ 38 - 41
packages/interpreter/src/bindings.rs

@@ -2,7 +2,7 @@
 
 use js_sys::Function;
 use wasm_bindgen::prelude::*;
-use web_sys::{Element, Node};
+use web_sys::Element;
 
 #[wasm_bindgen(module = "/src/interpreter.js")]
 extern "C" {
@@ -12,86 +12,83 @@ extern "C" {
     pub fn new(arg: Element) -> Interpreter;
 
     #[wasm_bindgen(method)]
-    pub fn SetNode(this: &Interpreter, id: usize, node: Node);
+    pub fn AppendChildren(this: &Interpreter, many: u32);
 
     #[wasm_bindgen(method)]
-    pub fn AppendChildren(this: &Interpreter, root: Option<u64>, children: Vec<u64>);
+    pub fn AssignId(this: &Interpreter, path: &[u8], id: u32);
 
     #[wasm_bindgen(method)]
-    pub fn ReplaceWith(this: &Interpreter, root: Option<u64>, nodes: Vec<u64>);
+    pub fn CreateElement(this: &Interpreter, tag: &str);
 
     #[wasm_bindgen(method)]
-    pub fn InsertAfter(this: &Interpreter, root: Option<u64>, nodes: Vec<u64>);
+    pub fn CreateElementNs(this: &Interpreter, tag: &str, ns: &str);
 
     #[wasm_bindgen(method)]
-    pub fn InsertBefore(this: &Interpreter, root: Option<u64>, nodes: Vec<u64>);
+    pub fn CreatePlaceholder(this: &Interpreter, id: u32);
 
     #[wasm_bindgen(method)]
-    pub fn Remove(this: &Interpreter, root: Option<u64>);
+    pub fn CreateStaticPlaceholder(this: &Interpreter);
 
     #[wasm_bindgen(method)]
-    pub fn CreateTextNode(this: &Interpreter, text: JsValue, root: Option<u64>);
+    pub fn CreateTextPlaceholder(this: &Interpreter);
 
     #[wasm_bindgen(method)]
-    pub fn CreateElement(this: &Interpreter, tag: &str, root: Option<u64>, children: u32);
+    pub fn CreateStaticText(this: &Interpreter, value: &str);
 
     #[wasm_bindgen(method)]
-    pub fn CreateElementNs(
-        this: &Interpreter,
-        tag: &str,
-        root: Option<u64>,
-        ns: &str,
-        children: u32,
-    );
+    pub fn CreateTextNode(this: &Interpreter, value: JsValue, id: u32);
 
     #[wasm_bindgen(method)]
-    pub fn CreatePlaceholder(this: &Interpreter, root: Option<u64>);
+    pub fn HydrateText(this: &Interpreter, path: &[u8], value: &str, id: u32);
 
     #[wasm_bindgen(method)]
-    pub fn NewEventListener(
-        this: &Interpreter,
-        name: &str,
-        root: Option<u64>,
-        handler: &Function,
-        bubbles: bool,
-    );
+    pub fn LoadTemplate(this: &Interpreter, name: &str, index: u32, id: u32);
 
     #[wasm_bindgen(method)]
-    pub fn RemoveEventListener(this: &Interpreter, root: Option<u64>, name: &str, bubbles: bool);
+    pub fn ReplaceWith(this: &Interpreter, id: u32, m: u32);
 
     #[wasm_bindgen(method)]
-    pub fn SetText(this: &Interpreter, root: Option<u64>, text: JsValue);
+    pub fn ReplacePlaceholder(this: &Interpreter, path: &[u8], m: u32);
 
     #[wasm_bindgen(method)]
-    pub fn SetAttribute(
-        this: &Interpreter,
-        root: Option<u64>,
-        field: &str,
-        value: JsValue,
-        ns: Option<&str>,
-    );
+    pub fn InsertAfter(this: &Interpreter, id: u32, n: u32);
+
+    #[wasm_bindgen(method)]
+    pub fn InsertBefore(this: &Interpreter, id: u32, n: u32);
+
+    #[wasm_bindgen(method)]
+    pub fn SaveTemplate(this: &Interpreter, name: &str, m: u32);
 
     #[wasm_bindgen(method)]
-    pub fn RemoveAttribute(this: &Interpreter, root: Option<u64>, field: &str, ns: Option<&str>);
+    pub fn SetAttribute(this: &Interpreter, id: u32, name: &str, value: JsValue, ns: Option<&str>);
 
     #[wasm_bindgen(method)]
-    pub fn CloneNode(this: &Interpreter, root: Option<u64>, new_id: u64);
+    pub fn SetStaticAttribute(this: &Interpreter, name: &str, value: JsValue, ns: Option<&str>);
 
     #[wasm_bindgen(method)]
-    pub fn CloneNodeChildren(this: &Interpreter, root: Option<u64>, new_ids: Vec<u64>);
+    pub fn SetBoolAttribute(this: &Interpreter, id: u32, name: &str, value: bool);
 
     #[wasm_bindgen(method)]
-    pub fn FirstChild(this: &Interpreter);
+    pub fn SetText(this: &Interpreter, id: u32, text: JsValue);
+
+    #[wasm_bindgen(method)]
+    pub fn NewEventListener(
+        this: &Interpreter,
+        name: &str,
+        id: u32,
+        handler: &Function,
+        bubbles: bool,
+    );
 
     #[wasm_bindgen(method)]
-    pub fn NextSibling(this: &Interpreter);
+    pub fn RemoveEventListener(this: &Interpreter, name: &str, id: u32);
 
     #[wasm_bindgen(method)]
-    pub fn ParentNode(this: &Interpreter);
+    pub fn RemoveAttribute(this: &Interpreter, id: u32, field: &str, ns: Option<&str>);
 
     #[wasm_bindgen(method)]
-    pub fn StoreWithId(this: &Interpreter, id: u64);
+    pub fn Remove(this: &Interpreter, id: u32);
 
     #[wasm_bindgen(method)]
-    pub fn SetLastNode(this: &Interpreter, id: u64);
+    pub fn PushRoot(this: &Interpreter, id: u32);
 }

+ 10 - 3
packages/interpreter/src/interpreter.js

@@ -265,7 +265,14 @@ export class Interpreter {
   SaveTemplate(name, m) {
     this.templates[name] = this.stack.splice(this.stack.length - m);
   }
+  CreateStaticText(text) {
+    this.CreateTextNode(text);
+  }
+  CreateTextPlaceholder() {
+    this.CreateRawText("placeholder");
+  }
   handleEdit(edit) {
+    console.log(edit);
     switch (edit.type) {
       case "AppendChildren":
         this.AppendChildren(edit.m);
@@ -283,13 +290,13 @@ export class Interpreter {
         this.CreatePlaceholder(edit.id);
         break;
       case "CreateStaticText":
-        this.CreateTextNode(edit.value);
+        this.CreateStaticText(edit.value)
         break;
       case "CreateStaticPlaceholder":
         this.CreateStaticPlaceholder();
         break;
       case "CreateTextPlaceholder":
-        this.CreateRawText("placeholder");
+        this.CreateTextPlaceholder();
         break;
       case "CreateStaticText":
         this.CreateRawText(edit.value);
@@ -301,7 +308,7 @@ export class Interpreter {
         this.HydrateText(edit.path, edit.value, edit.id);
         break;
       case "LoadTemplate":
-        this.LoadTemplate(edit.name, edit.index);
+        this.LoadTemplate(edit.name, edit.index, edit.id);
         break;
       case "SaveTemplate":
         this.SaveTemplate(edit.name, edit.m);

+ 1 - 1
packages/web/Cargo.toml

@@ -77,7 +77,7 @@ features = [
 ]
 
 [features]
-default = ["panic_hook", "hydrate"]
+default = ["panic_hook"]
 panic_hook = ["console_error_panic_hook"]
 hydrate = []
 

+ 167 - 308
packages/web/src/dom.rs

@@ -7,11 +7,12 @@
 //! - tests to ensure dyn_into works for various event types.
 //! - Partial delegation?>
 
-use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent};
-use dioxus_html::event_bubbles;
+use dioxus_core::{ElementId, Mutation, Mutations};
+use dioxus_html::{event_bubbles, CompositionData, FormData};
 use dioxus_interpreter_js::Interpreter;
+use futures_channel::mpsc;
 use js_sys::Function;
-use std::{any::Any, rc::Rc, sync::Arc};
+use std::{any::Any, rc::Rc};
 use wasm_bindgen::{closure::Closure, JsCast, JsValue};
 use web_sys::{Document, Element, Event, HtmlElement};
 
@@ -26,68 +27,26 @@ pub struct WebsysDom {
 }
 
 impl WebsysDom {
-    pub fn new(cfg: Config, sender_callback: Rc<dyn Fn(SchedulerMsg)>) -> Self {
+    pub fn new(cfg: Config, event_channel: mpsc::UnboundedSender<Event>) -> Self {
         // eventually, we just want to let the interpreter do all the work of decoding events into our event type
         let callback: Box<dyn FnMut(&Event)> = Box::new(move |event: &web_sys::Event| {
-            let mut target = event
-                .target()
-                .expect("missing target")
-                .dyn_into::<Element>()
-                .expect("not a valid element");
+            _ = event_channel.unbounded_send(event.clone());
 
-            let typ = event.type_();
+            // if let Ok(synthetic_event) = decoded {
+            //     // Try to prevent default if the attribute is set
+            //     if let Some(node) = target.dyn_ref::<HtmlElement>() {
+            //         if let Some(name) = node.get_attribute("dioxus-prevent-default") {
+            //             if name == synthetic_event.name
+            //                 || name.trim_start_matches("on") == synthetic_event.name
+            //             {
+            //                 log::trace!("Preventing default");
+            //                 event.prevent_default();
+            //             }
+            //         }
+            //     }
 
-            let decoded: anyhow::Result<UserEvent> = loop {
-                match target.get_attribute("data-dioxus-id").map(|f| f.parse()) {
-                    Some(Ok(id)) => {
-                        break Ok(UserEvent {
-                            name: event_name_from_typ(&typ),
-                            data: virtual_event_from_websys_event(event.clone(), target.clone()),
-                            element: Some(ElementId(id)),
-                            scope_id: None,
-                            priority: dioxus_core::EventPriority::Medium,
-                            bubbles: event.bubbles(),
-                        });
-                    }
-                    Some(Err(e)) => {
-                        break Err(e.into());
-                    }
-                    None => {
-                        // walk the tree upwards until we actually find an event target
-                        if let Some(parent) = target.parent_element() {
-                            target = parent;
-                        } else {
-                            break Ok(UserEvent {
-                                name: event_name_from_typ(&typ),
-                                data: virtual_event_from_websys_event(
-                                    event.clone(),
-                                    target.clone(),
-                                ),
-                                element: None,
-                                scope_id: None,
-                                priority: dioxus_core::EventPriority::Low,
-                                bubbles: event.bubbles(),
-                            });
-                        }
-                    }
-                }
-            };
-
-            if let Ok(synthetic_event) = decoded {
-                // Try to prevent default if the attribute is set
-                if let Some(node) = target.dyn_ref::<HtmlElement>() {
-                    if let Some(name) = node.get_attribute("dioxus-prevent-default") {
-                        if name == synthetic_event.name
-                            || name.trim_start_matches("on") == synthetic_event.name
-                        {
-                            log::trace!("Preventing default");
-                            event.prevent_default();
-                        }
-                    }
-                }
-
-                sender_callback.as_ref()(SchedulerMsg::Event(synthetic_event))
-            }
+            //     sender_callback.as_ref()(SchedulerMsg::Event(synthetic_event))
+            // }
         });
 
         // a match here in order to avoid some error during runtime browser test
@@ -104,75 +63,56 @@ impl WebsysDom {
         }
     }
 
-    pub fn apply_edits(&mut self, mut edits: Vec<DomEdit>) {
+    pub fn apply_edits(&mut self, mut edits: Vec<Mutation>) {
+        use Mutation::*;
+        let i = &self.interpreter;
+
         for edit in edits.drain(..) {
             match edit {
-                DomEdit::AppendChildren { root, children } => {
-                    self.interpreter.AppendChildren(root, children);
-                }
-                DomEdit::ReplaceWith { root, nodes } => self.interpreter.ReplaceWith(root, nodes),
-                DomEdit::InsertAfter { root, nodes } => self.interpreter.InsertAfter(root, nodes),
-                DomEdit::InsertBefore { root, nodes } => self.interpreter.InsertBefore(root, nodes),
-                DomEdit::Remove { root } => self.interpreter.Remove(root),
-
-                DomEdit::CreateElement {
-                    root,
-                    tag,
-                    children,
-                } => self.interpreter.CreateElement(tag, root, children),
-                DomEdit::CreateElementNs {
-                    root,
-                    tag,
+                AppendChildren { m } => i.AppendChildren(m as u32),
+                AssignId { path, id } => i.AssignId(path, id.0 as u32),
+                CreateElement { name } => i.CreateElement(name),
+                CreateElementNamespace { name, namespace } => i.CreateElementNs(name, namespace),
+                CreatePlaceholder { id } => i.CreatePlaceholder(id.0 as u32),
+                CreateStaticPlaceholder => i.CreateStaticPlaceholder(),
+                CreateTextPlaceholder => i.CreateTextPlaceholder(),
+                CreateStaticText { value } => i.CreateStaticText(value),
+                CreateTextNode { value, id } => i.CreateTextNode(value.into(), id.0 as u32),
+                HydrateText { path, value, id } => i.HydrateText(path, value, id.0 as u32),
+                LoadTemplate { name, index, id } => i.LoadTemplate(name, index as u32, id.0 as u32),
+                ReplaceWith { id, m } => i.ReplaceWith(id.0 as u32, m as u32),
+                ReplacePlaceholder { path, m } => i.ReplacePlaceholder(path, m as u32),
+                InsertAfter { id, m } => i.InsertAfter(id.0 as u32, m as u32),
+                InsertBefore { id, m } => i.InsertBefore(id.0 as u32, m as u32),
+                SaveTemplate { name, m } => i.SaveTemplate(name, m as u32),
+                SetAttribute {
+                    name,
+                    value,
+                    id,
                     ns,
-                    children,
-                } => self.interpreter.CreateElementNs(tag, root, ns, children),
-                DomEdit::CreatePlaceholder { root } => self.interpreter.CreatePlaceholder(root),
-                DomEdit::NewEventListener {
-                    event_name, root, ..
-                } => {
+                } => i.SetAttribute(id.0 as u32, name, value.into(), ns),
+                SetStaticAttribute { name, value, ns } => {
+                    i.SetStaticAttribute(name, value.into(), ns)
+                }
+                SetBoolAttribute { name, value, id } => {
+                    i.SetBoolAttribute(id.0 as u32, name, value)
+                }
+                SetText { value, id } => i.SetText(id.0 as u32, value.into()),
+                NewEventListener { name, scope, id } => {
                     let handler: &Function = self.handler.as_ref().unchecked_ref();
                     self.interpreter.NewEventListener(
-                        event_name,
-                        root,
+                        name,
+                        id.0 as u32,
                         handler,
-                        event_bubbles(event_name),
+                        event_bubbles(&name[2..]),
                     );
                 }
-
-                DomEdit::RemoveEventListener { root, event } => self
-                    .interpreter
-                    .RemoveEventListener(root, event, event_bubbles(event)),
-
-                DomEdit::RemoveAttribute { root, name, ns } => {
-                    self.interpreter.RemoveAttribute(root, name, ns)
-                }
-
-                DomEdit::CreateTextNode { text, root } => {
-                    let text = JsValue::from_str(text);
-                    self.interpreter.CreateTextNode(text, root)
-                }
-                DomEdit::SetText { root, text } => {
-                    let text = JsValue::from_str(text);
-                    self.interpreter.SetText(root, text)
-                }
-                DomEdit::SetAttribute {
-                    root,
-                    field,
-                    value,
-                    ns,
-                } => {
-                    let value = JsValue::from_str(&value.to_string());
-                    self.interpreter.SetAttribute(root, field, value, ns)
-                }
-                DomEdit::CloneNode { id, new_id } => self.interpreter.CloneNode(id, new_id),
-                DomEdit::CloneNodeChildren { id, new_ids } => {
-                    self.interpreter.CloneNodeChildren(id, new_ids)
-                }
-                DomEdit::FirstChild {} => self.interpreter.FirstChild(),
-                DomEdit::NextSibling {} => self.interpreter.NextSibling(),
-                DomEdit::ParentNode {} => self.interpreter.ParentNode(),
-                DomEdit::StoreWithId { id } => self.interpreter.StoreWithId(id),
-                DomEdit::SetLastNode { id } => self.interpreter.SetLastNode(id),
+                RemoveEventListener { name, id } => i.RemoveEventListener(name, id.0 as u32),
+                Remove { id } => i.Remove(id.0 as u32),
+                PushRoot { id } => i.PushRoot(id.0 as u32),
+                // Mutation::RemoveEventListener { root, name: event } => self
+                //     .interpreter
+                //     .RemoveEventListener(root, event, event_bubbles(event)),
             }
         }
     }
@@ -187,129 +127,54 @@ unsafe impl Sync for DioxusWebsysEvent {}
 
 // todo: some of these events are being casted to the wrong event type.
 // We need tests that simulate clicks/etc and make sure every event type works.
-fn virtual_event_from_websys_event(
-    event: web_sys::Event,
-    target: Element,
-) -> Arc<dyn Any + Send + Sync> {
-    use dioxus_html::on::*;
+pub fn virtual_event_from_websys_event(event: web_sys::Event, target: Element) -> Rc<dyn Any> {
+    use dioxus_html::events::*;
 
     match event.type_().as_str() {
-        "copy" | "cut" | "paste" => Arc::new(ClipboardData {}),
+        "copy" | "cut" | "paste" => Rc::new(ClipboardData {}),
         "compositionend" | "compositionstart" | "compositionupdate" => {
-            let evt: &web_sys::CompositionEvent = event.dyn_ref().unwrap();
-            Arc::new(CompositionData {
-                data: evt.data().unwrap_or_default(),
-            })
+            make_composition_event(&event)
         }
-        "keydown" | "keypress" | "keyup" => Arc::new(KeyboardData::from(event)),
-        "focus" | "blur" | "focusout" | "focusin" => Arc::new(FocusData {}),
+        "keydown" | "keypress" | "keyup" => Rc::new(KeyboardData::from(event)),
+        "focus" | "blur" | "focusout" | "focusin" => Rc::new(FocusData {}),
 
-        // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
-        // don't have a good solution with the serialized event problem
-        "change" | "input" | "invalid" | "reset" | "submit" => {
-            let value: String = target
-                .dyn_ref()
-                .map(|input: &web_sys::HtmlInputElement| {
-                    // todo: special case more input types
-                    match input.type_().as_str() {
-                        "checkbox" => {
-                           match input.checked() {
-                                true => "true".to_string(),
-                                false => "false".to_string(),
-                            }
-                        },
-                        _ => {
-                            input.value()
-                        }
-                    }
-                })
-                .or_else(|| {
-                    target
-                        .dyn_ref()
-                        .map(|input: &web_sys::HtmlTextAreaElement| input.value())
-                })
-                // select elements are NOT input events - because - why woudn't they be??
-                .or_else(|| {
-                    target
-                        .dyn_ref()
-                        .map(|input: &web_sys::HtmlSelectElement| input.value())
-                })
-                .or_else(|| {
-                    target
-                        .dyn_ref::<web_sys::HtmlElement>()
-                        .unwrap()
-                        .text_content()
-                })
-                .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
+        "change" | "input" | "invalid" | "reset" | "submit" => read_input_to_data(target),
 
-            let mut values = std::collections::HashMap::new();
-
-            // try to fill in form values
-            if let Some(form) = target.dyn_ref::<web_sys::HtmlFormElement>() {
-                let elements = form.elements();
-                for x in 0..elements.length() {
-                    let element = elements.item(x).unwrap();
-                    if let Some(name) = element.get_attribute("name") {
-                        let value: Option<String> = element
-                                .dyn_ref()
-                                .map(|input: &web_sys::HtmlInputElement| {
-                                    match input.type_().as_str() {
-                                        "checkbox" => {
-                                            match input.checked() {
-                                                true => Some("true".to_string()),
-                                                false => Some("false".to_string()),
-                                            }
-                                        },
-                                        "radio" => {
-                                            match input.checked() {
-                                                true => Some(input.value()),
-                                                false => None,
-                                            }
-                                        }
-                                        _ => Some(input.value())
-                                    }
-                                })
-                                .or_else(|| element.dyn_ref().map(|input: &web_sys::HtmlTextAreaElement| Some(input.value())))
-                                .or_else(|| element.dyn_ref().map(|input: &web_sys::HtmlSelectElement| Some(input.value())))
-                                .or_else(|| Some(element.dyn_ref::<web_sys::HtmlElement>().unwrap().text_content()))
-                                .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
-                        if let Some(value) = value {
-                            values.insert(name, value);
-                        }
-                    }
-                }
-            }
-
-            Arc::new(FormData { value, values })
-        }
         "click" | "contextmenu" | "dblclick" | "doubleclick" | "drag" | "dragend" | "dragenter"
         | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown"
         | "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
-            Arc::new(MouseData::from(event))
+            Rc::new(MouseData::from(event))
         }
         "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
         | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
-            Arc::new(PointerData::from(event))
+            Rc::new(PointerData::from(event))
         }
-        "select" => Arc::new(SelectionData {}),
-        "touchcancel" | "touchend" | "touchmove" | "touchstart" => Arc::new(TouchData::from(event)),
+        "select" => Rc::new(SelectionData {}),
+        "touchcancel" | "touchend" | "touchmove" | "touchstart" => Rc::new(TouchData::from(event)),
 
-        "scroll" => Arc::new(()),
-        "wheel" => Arc::new(WheelData::from(event)),
+        "scroll" => Rc::new(()),
+        "wheel" => Rc::new(WheelData::from(event)),
         "animationstart" | "animationend" | "animationiteration" => {
-            Arc::new(AnimationData::from(event))
+            Rc::new(AnimationData::from(event))
         }
-        "transitionend" => Arc::new(TransitionData::from(event)),
+        "transitionend" => Rc::new(TransitionData::from(event)),
         "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
         | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
         | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
-        | "timeupdate" | "volumechange" | "waiting" => Arc::new(MediaData {}),
-        "toggle" => Arc::new(ToggleData {}),
+        | "timeupdate" | "volumechange" | "waiting" => Rc::new(MediaData {}),
+        "toggle" => Rc::new(ToggleData {}),
 
-        _ => Arc::new(()),
+        _ => Rc::new(()),
     }
 }
 
+fn make_composition_event(event: &Event) -> Rc<CompositionData> {
+    let evt: &web_sys::CompositionEvent = event.dyn_ref().unwrap();
+    Rc::new(CompositionData {
+        data: evt.data().unwrap_or_default(),
+    })
+}
+
 pub(crate) fn load_document() -> Document {
     web_sys::window()
         .expect("should have access to the Window")
@@ -317,92 +182,86 @@ pub(crate) fn load_document() -> Document {
         .expect("should have access to the Document")
 }
 
-fn event_name_from_typ(typ: &str) -> &'static str {
-    match typ {
-        "copy" => "copy",
-        "cut" => "cut",
-        "paste" => "paste",
-        "compositionend" => "compositionend",
-        "compositionstart" => "compositionstart",
-        "compositionupdate" => "compositionupdate",
-        "keydown" => "keydown",
-        "keypress" => "keypress",
-        "keyup" => "keyup",
-        "focus" => "focus",
-        "focusout" => "focusout",
-        "focusin" => "focusin",
-        "blur" => "blur",
-        "change" => "change",
-        "input" => "input",
-        "invalid" => "invalid",
-        "reset" => "reset",
-        "submit" => "submit",
-        "click" => "click",
-        "contextmenu" => "contextmenu",
-        "doubleclick" => "doubleclick",
-        "dblclick" => "dblclick",
-        "drag" => "drag",
-        "dragend" => "dragend",
-        "dragenter" => "dragenter",
-        "dragexit" => "dragexit",
-        "dragleave" => "dragleave",
-        "dragover" => "dragover",
-        "dragstart" => "dragstart",
-        "drop" => "drop",
-        "mousedown" => "mousedown",
-        "mouseenter" => "mouseenter",
-        "mouseleave" => "mouseleave",
-        "mousemove" => "mousemove",
-        "mouseout" => "mouseout",
-        "mouseover" => "mouseover",
-        "mouseup" => "mouseup",
-        "pointerdown" => "pointerdown",
-        "pointermove" => "pointermove",
-        "pointerup" => "pointerup",
-        "pointercancel" => "pointercancel",
-        "gotpointercapture" => "gotpointercapture",
-        "lostpointercapture" => "lostpointercapture",
-        "pointerenter" => "pointerenter",
-        "pointerleave" => "pointerleave",
-        "pointerover" => "pointerover",
-        "pointerout" => "pointerout",
-        "select" => "select",
-        "touchcancel" => "touchcancel",
-        "touchend" => "touchend",
-        "touchmove" => "touchmove",
-        "touchstart" => "touchstart",
-        "scroll" => "scroll",
-        "wheel" => "wheel",
-        "animationstart" => "animationstart",
-        "animationend" => "animationend",
-        "animationiteration" => "animationiteration",
-        "transitionend" => "transitionend",
-        "abort" => "abort",
-        "canplay" => "canplay",
-        "canplaythrough" => "canplaythrough",
-        "durationchange" => "durationchange",
-        "emptied" => "emptied",
-        "encrypted" => "encrypted",
-        "ended" => "ended",
-        "error" => "error",
-        "loadeddata" => "loadeddata",
-        "loadedmetadata" => "loadedmetadata",
-        "loadstart" => "loadstart",
-        "pause" => "pause",
-        "play" => "play",
-        "playing" => "playing",
-        "progress" => "progress",
-        "ratechange" => "ratechange",
-        "seeked" => "seeked",
-        "seeking" => "seeking",
-        "stalled" => "stalled",
-        "suspend" => "suspend",
-        "timeupdate" => "timeupdate",
-        "volumechange" => "volumechange",
-        "waiting" => "waiting",
-        "toggle" => "toggle",
-        a => {
-            panic!("unsupported event type {:?}", a);
+fn read_input_to_data(target: Element) -> Rc<FormData> {
+    // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
+    // don't have a good solution with the serialized event problem
+
+    let value: String = target
+        .dyn_ref()
+        .map(|input: &web_sys::HtmlInputElement| {
+            // todo: special case more input types
+            match input.type_().as_str() {
+                "checkbox" => {
+                    match input.checked() {
+                        true => "true".to_string(),
+                        false => "false".to_string(),
+                    }
+                },
+                _ => {
+                    input.value()
+                }
+            }
+        })
+        .or_else(|| {
+            target
+                .dyn_ref()
+                .map(|input: &web_sys::HtmlTextAreaElement| input.value())
+        })
+        // select elements are NOT input events - because - why woudn't they be??
+        .or_else(|| {
+            target
+                .dyn_ref()
+                .map(|input: &web_sys::HtmlSelectElement| input.value())
+        })
+        .or_else(|| {
+            target
+                .dyn_ref::<web_sys::HtmlElement>()
+                .unwrap()
+                .text_content()
+        })
+        .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
+
+    let mut values = std::collections::HashMap::new();
+
+    // try to fill in form values
+    if let Some(form) = target.dyn_ref::<web_sys::HtmlFormElement>() {
+        let elements = form.elements();
+        for x in 0..elements.length() {
+            let element = elements.item(x).unwrap();
+            if let Some(name) = element.get_attribute("name") {
+                let value: Option<String> = element
+                    .dyn_ref()
+                    .map(|input: &web_sys::HtmlInputElement| {
+                        match input.type_().as_str() {
+                            "checkbox" => {
+                                match input.checked() {
+                                    true => Some("true".to_string()),
+                                    false => Some("false".to_string()),
+                                }
+                            },
+                            "radio" => {
+                                match input.checked() {
+                                    true => Some(input.value()),
+                                    false => None,
+                                }
+                            }
+                            _ => Some(input.value())
+                        }
+                    })
+                    .or_else(|| element.dyn_ref().map(|input: &web_sys::HtmlTextAreaElement| Some(input.value())))
+                    .or_else(|| element.dyn_ref().map(|input: &web_sys::HtmlSelectElement| Some(input.value())))
+                    .or_else(|| Some(element.dyn_ref::<web_sys::HtmlElement>().unwrap().text_content()))
+                    .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
+                if let Some(value) = value {
+                    values.insert(name, value);
+                }
+            }
         }
     }
+
+    Rc::new(FormData {
+        value,
+        values,
+        files: None,
+    })
 }

+ 127 - 53
packages/web/src/lib.rs

@@ -54,21 +54,23 @@
 //     - Do the VDOM work during the idlecallback
 //     - Do DOM work in the next requestAnimationFrame callback
 
-use std::rc::Rc;
+use std::{rc::Rc, time::Duration};
 
 pub use crate::cfg::Config;
+use crate::dom::virtual_event_from_websys_event;
 pub use crate::util::use_eval;
-use dioxus_core::prelude::Component;
-use dioxus_core::SchedulerMsg;
-use dioxus_core::VirtualDom;
+use dioxus_core::{Element, ElementId, Scope, VirtualDom};
+use futures_util::{pin_mut, FutureExt, StreamExt};
+use gloo_timers::future::sleep;
+use web_sys::Event;
 
 mod cache;
 mod cfg;
 mod dom;
-#[cfg(any(feature = "hot-reload", debug_assertions))]
-mod hot_reload;
-#[cfg(feature = "hydrate")]
-mod rehydrate;
+// #[cfg(any(feature = "hot-reload", debug_assertions))]
+// mod hot_reload;
+// #[cfg(feature = "hydrate")]
+// mod rehydrate;
 // mod ric_raf;
 mod util;
 
@@ -92,7 +94,7 @@ mod util;
 ///     render!(div {"hello world"})
 /// }
 /// ```
-pub fn launch(root_component: Component) {
+pub fn launch(root_component: fn(Scope) -> Element) {
     launch_with_props(root_component, (), Config::default());
 }
 
@@ -115,7 +117,7 @@ pub fn launch(root_component: Component) {
 ///     })
 /// }
 /// ```
-pub fn launch_cfg(root: Component, config: Config) {
+pub fn launch_cfg(root: fn(Scope) -> Element, config: Config) {
     launch_with_props(root, (), config)
 }
 
@@ -143,10 +145,11 @@ pub fn launch_cfg(root: Component, config: Config) {
 ///     render!(div {"hello {cx.props.name}"})
 /// }
 /// ```
-pub fn launch_with_props<T>(root_component: Component<T>, root_properties: T, config: Config)
-where
-    T: Send + 'static,
-{
+pub fn launch_with_props<T: 'static>(
+    root_component: fn(Scope<T>) -> Element,
+    root_properties: T,
+    config: Config,
+) {
     wasm_bindgen_futures::spawn_local(run_with_props(root_component, root_properties, config));
 }
 
@@ -162,7 +165,9 @@ where
 ///     wasm_bindgen_futures::spawn_local(app_fut);
 /// }
 /// ```
-pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T, cfg: Config) {
+pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_props: T, cfg: Config) {
+    log::info!("Starting up");
+
     let mut dom = VirtualDom::new_with_props(root, root_props);
 
     #[cfg(feature = "panic_hook")]
@@ -170,8 +175,8 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
         console_error_panic_hook::set_once();
     }
 
-    #[cfg(any(feature = "hot-reload", debug_assertions))]
-    hot_reload::init(&dom);
+    // #[cfg(any(feature = "hot-reload", debug_assertions))]
+    // hot_reload::init(&dom);
 
     for s in crate::cache::BUILTIN_INTERNED_STRINGS {
         wasm_bindgen::intern(s);
@@ -180,50 +185,48 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
         wasm_bindgen::intern(s);
     }
 
-    let tasks = dom.get_scheduler_channel();
+    // a    let should_hydrate = cfg.hydrate;
 
-    let sender_callback: Rc<dyn Fn(SchedulerMsg)> =
-        Rc::new(move |event| tasks.unbounded_send(event).unwrap());
+    let (tx, mut rx) = futures_channel::mpsc::unbounded();
 
-    let should_hydrate = cfg.hydrate;
+    let mut websys_dom = dom::WebsysDom::new(cfg, tx);
 
-    let mut websys_dom = dom::WebsysDom::new(cfg, sender_callback);
+    log::info!("rebuilding app");
 
-    log::trace!("rebuilding app");
+    let edits = dom.rebuild();
+    websys_dom.apply_edits(edits.template_mutations);
+    websys_dom.apply_edits(edits.edits);
 
-    if should_hydrate {
-        // todo: we need to split rebuild and initialize into two phases
-        // it's a waste to produce edits just to get the vdom loaded
-        let _ = dom.rebuild();
+    loop {
+        log::trace!("waiting for work");
+        // if virtualdom has nothing, wait for it to have something before requesting idle time
+        // if there is work then this future resolves immediately.
 
-        #[cfg(feature = "hydrate")]
-        #[allow(unused_variables)]
-        if let Err(err) = websys_dom.rehydrate(&dom) {
-            log::error!(
-                "Rehydration failed {:?}. Rebuild DOM into element from scratch",
-                &err
-            );
+        let mut res = {
+            let work = dom.wait_for_work().fuse();
+            pin_mut!(work);
 
-            websys_dom.root.set_text_content(None);
+            futures_util::select! {
+                _ = work => None,
+                evt = rx.next() => evt
+            }
+        };
 
-            // errrrr we should split rebuild into two phases
-            // one that initializes things and one that produces edits
-            let edits = dom.rebuild();
+        while let Some(evt) = res {
+            let name = evt.type_();
+            let element = walk_event_for_id(&evt);
+            let bubbles = dioxus_html::event_bubbles(name.as_str());
 
-            websys_dom.apply_edits(edits.edits);
+            if let Some((element, target)) = element {
+                let data = virtual_event_from_websys_event(evt, target);
+                dom.handle_event(name.as_str(), data, element, bubbles);
+            }
+            res = rx.try_next().transpose().unwrap().ok();
         }
-    } else {
-        let edits = dom.rebuild();
-        websys_dom.apply_edits(edits.edits);
-    }
 
-    // let mut work_loop = ric_raf::RafLoop::new();
+        let deadline = sleep(Duration::from_millis(50));
 
-    loop {
-        log::trace!("waiting for work");
-        // if virtualdom has nothing, wait for it to have something before requesting idle time
-        // if there is work then this future resolves immediately.
-        dom.wait_for_work().await;
+        let edits = dom.render_with_deadline(deadline).await;
 
         log::trace!("working..");
 
@@ -232,14 +235,85 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
 
         // run the virtualdom work phase until the frame deadline is reached
         // let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
-        let mutations = dom.work_with_deadline(|| false);
 
         // wait for the animation frame to fire so we can apply our changes
         // work_loop.wait_for_raf().await;
 
-        for edit in mutations {
-            // actually apply our changes during the animation frame
-            websys_dom.apply_edits(edit.edits);
+        // for edit in mutations {
+        //     // actually apply our changes during the animation frame
+        websys_dom.apply_edits(edits.template_mutations);
+        websys_dom.apply_edits(edits.edits);
+        // }
+    }
+}
+
+fn walk_event_for_id(event: &Event) -> Option<(ElementId, web_sys::Element)> {
+    use wasm_bindgen::{closure::Closure, JsCast, JsValue};
+    use web_sys::{Document, Element, Event, HtmlElement};
+
+    let mut target = event
+        .target()
+        .expect("missing target")
+        .dyn_into::<Element>()
+        .expect("not a valid element");
+
+    // break Ok(UserEvent {
+    //     name: event_name_from_typ(&typ),
+    //     data: virtual_event_from_websys_event(event.clone(), target.clone()),
+    //     element: Some(ElementId(id)),
+    //     scope_id: None,
+    //     priority: dioxus_core::EventPriority::Medium,
+    //     bubbles: event.bubbles(),
+    // });
+
+    // break Ok(UserEvent {
+    //     name: event_name_from_typ(&typ),
+    //     data: virtual_event_from_websys_event(event.clone(), target.clone()),
+    //     element: None,
+    //     scope_id: None,
+    //     priority: dioxus_core::EventPriority::Low,
+    //     bubbles: event.bubbles(),
+    // });
+
+    loop {
+        match target.get_attribute("data-dioxus-id").map(|f| f.parse()) {
+            Some(Ok(id)) => return Some((ElementId(id), target)),
+            Some(Err(_)) => return None,
+
+            // walk the tree upwards until we actually find an event target
+            None => match target.parent_element() {
+                Some(parent) => target = parent,
+                None => return None,
+            },
         }
     }
 }
+
+// if should_hydrate {
+//     // todo: we need to split rebuild and initialize into two phases
+//     // it's a waste to produce edits just to get the vdom loaded
+//     let _ = dom.rebuild();
+
+//     #[cfg(feature = "hydrate")]
+//     #[allow(unused_variables)]
+//     if let Err(err) = websys_dom.rehydrate(&dom) {
+//         log::error!(
+//             "Rehydration failed {:?}. Rebuild DOM into element from scratch",
+//             &err
+//         );
+
+//         websys_dom.root.set_text_content(None);
+
+//         // errrrr we should split rebuild into two phases
+//         // one that initializes things and one that produces edits
+//         let edits = dom.rebuild();
+
+//         websys_dom.apply_edits(edits.edits);
+//     }
+// } else {
+//     let edits = dom.rebuild();
+//     websys_dom.apply_edits(edits.template_mutations);
+//     websys_dom.apply_edits(edits.edits);
+// }
+
+// let mut work_loop = ric_raf::RafLoop::new();

+ 0 - 856
packages/web/src/olddom.rs

@@ -1,856 +0,0 @@
-//! Implementation of a renderer for Dioxus on the web.
-//!
-//! Oustanding todos:
-//! - Removing event listeners (delegation)
-//! - Passive event listeners
-//! - no-op event listener patch for safari
-//! - tests to ensure dyn_into works for various event types.
-//! - Partial delegation?>
-
-use dioxus_core::{DomEdit, ElementId, SchedulerMsg, ScopeId, UserEvent};
-use rustc_hash::FxHashMap;
-use std::{any::Any, fmt::Debug, rc::Rc, sync::Arc};
-use wasm_bindgen::{closure::Closure, JsCast};
-use web_sys::{
-    CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement,
-    HtmlOptionElement, HtmlTextAreaElement, Node,
-};
-
-use crate::{nodeslab::NodeSlab, Config};
-
-pub struct WebsysDom {
-    stack: Stack,
-
-    /// A map from ElementID (index) to Node
-    pub(crate) nodes: NodeSlab,
-
-    document: Document,
-
-    pub(crate) root: Element,
-
-    sender_callback: Rc<dyn Fn(SchedulerMsg)>,
-
-    // map of listener types to number of those listeners
-    // This is roughly a delegater
-    // TODO: check how infero delegates its events - some are more performant
-    listeners: FxHashMap<&'static str, ListenerEntry>,
-}
-
-type ListenerEntry = (usize, Closure<dyn FnMut(&Event)>);
-
-impl WebsysDom {
-    pub fn new(cfg: Config, sender_callback: Rc<dyn Fn(SchedulerMsg)>) -> Self {
-        let document = load_document();
-
-        let nodes = NodeSlab::new(2000);
-        let listeners = FxHashMap::default();
-
-        let mut stack = Stack::with_capacity(10);
-
-        let root = load_document().get_element_by_id(&cfg.rootname).unwrap();
-        let root_node = root.clone().dyn_into::<Node>().unwrap();
-        stack.push(root_node);
-
-        Self {
-            stack,
-            nodes,
-            listeners,
-            document,
-            sender_callback,
-            root,
-        }
-    }
-
-    pub fn apply_edits(&mut self, mut edits: Vec<DomEdit>) {
-        for edit in edits.drain(..) {
-            match edit {
-                DomEdit::PushRoot { root } => self.push(root),
-                DomEdit::AppendChildren { many } => self.append_children(many),
-                DomEdit::ReplaceWith { m, root } => self.replace_with(m, root),
-                DomEdit::Remove { root } => self.remove(root),
-                DomEdit::CreateTextNode { text, root: id } => self.create_text_node(text, id),
-                DomEdit::CreateElement { tag, root: id } => self.create_element(tag, None, id),
-                DomEdit::CreateElementNs { tag, root: id, ns } => {
-                    self.create_element(tag, Some(ns), id)
-                }
-                DomEdit::CreatePlaceholder { root: id } => self.create_placeholder(id),
-                DomEdit::NewEventListener {
-                    event_name,
-                    scope,
-                    root: mounted_node_id,
-                } => self.new_event_listener(event_name, scope, mounted_node_id),
-
-                DomEdit::RemoveEventListener { event, root } => {
-                    self.remove_event_listener(event, root)
-                }
-
-                DomEdit::SetText { text, root } => self.set_text(text, root),
-                DomEdit::SetAttribute {
-                    field,
-                    value,
-                    ns,
-                    root,
-                } => self.set_attribute(field, value, ns, root),
-                DomEdit::RemoveAttribute { name, root } => self.remove_attribute(name, root),
-
-                DomEdit::InsertAfter { n, root } => self.insert_after(n, root),
-                DomEdit::InsertBefore { n, root } => self.insert_before(n, root),
-            }
-        }
-    }
-    fn push(&mut self, root: u64) {
-        let key = root as usize;
-        let domnode = &self.nodes[key];
-
-        let real_node: Node = match domnode {
-            Some(n) => n.clone(),
-            None => todo!(),
-        };
-
-        self.stack.push(real_node);
-    }
-
-    fn append_children(&mut self, many: u32) {
-        let root: Node = self
-            .stack
-            .list
-            .get(self.stack.list.len() - (1 + many as usize))
-            .unwrap()
-            .clone();
-
-        // We need to make sure to add comments between text nodes
-        // We ensure that the text siblings are patched by preventing the browser from merging
-        // neighboring text nodes. Originally inspired by some of React's work from 2016.
-        //  -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
-        //  -> https://github.com/facebook/react/pull/5753
-        /*
-        todo: we need to track this for replacing/insert after/etc
-        */
-        let mut last_node_was_text = false;
-
-        for child in self
-            .stack
-            .list
-            .drain((self.stack.list.len() - many as usize)..)
-        {
-            if child.dyn_ref::<web_sys::Text>().is_some() {
-                if last_node_was_text {
-                    let comment_node = self
-                        .document
-                        .create_comment("dioxus")
-                        .dyn_into::<Node>()
-                        .unwrap();
-                    root.append_child(&comment_node).unwrap();
-                }
-                last_node_was_text = true;
-            } else {
-                last_node_was_text = false;
-            }
-            root.append_child(&child).unwrap();
-        }
-    }
-
-    fn replace_with(&mut self, m: u32, root: u64) {
-        let old = self.nodes[root as usize].as_ref().unwrap();
-
-        let arr: js_sys::Array = self
-            .stack
-            .list
-            .drain((self.stack.list.len() - m as usize)..)
-            .collect();
-
-        if let Some(el) = old.dyn_ref::<Element>() {
-            el.replace_with_with_node(&arr).unwrap();
-        } else if let Some(el) = old.dyn_ref::<web_sys::CharacterData>() {
-            el.replace_with_with_node(&arr).unwrap();
-        } else if let Some(el) = old.dyn_ref::<web_sys::DocumentType>() {
-            el.replace_with_with_node(&arr).unwrap();
-        }
-    }
-
-    fn remove(&mut self, root: u64) {
-        let node = self.nodes[root as usize].as_ref().unwrap();
-        if let Some(element) = node.dyn_ref::<Element>() {
-            element.remove();
-        } else {
-            if let Some(parent) = node.parent_node() {
-                parent.remove_child(&node).unwrap();
-            }
-        }
-    }
-
-    fn create_placeholder(&mut self, id: u64) {
-        self.create_element("pre", None, id);
-        self.set_attribute("hidden", "", None, id);
-    }
-
-    fn create_text_node(&mut self, text: &str, id: u64) {
-        let textnode = self
-            .document
-            .create_text_node(text)
-            .dyn_into::<Node>()
-            .unwrap();
-
-        self.stack.push(textnode.clone());
-
-        self.nodes[(id as usize)] = Some(textnode);
-    }
-
-    fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) {
-        let tag = wasm_bindgen::intern(tag);
-
-        let el = match ns {
-            Some(ns) => self
-                .document
-                .create_element_ns(Some(ns), tag)
-                .unwrap()
-                .dyn_into::<Node>()
-                .unwrap(),
-            None => self
-                .document
-                .create_element(tag)
-                .unwrap()
-                .dyn_into::<Node>()
-                .unwrap(),
-        };
-
-        use smallstr::SmallString;
-        use std::fmt::Write;
-
-        let mut s: SmallString<[u8; 8]> = smallstr::SmallString::new();
-        write!(s, "{}", id).unwrap();
-
-        let el2 = el.dyn_ref::<Element>().unwrap();
-        el2.set_attribute("dioxus-id", s.as_str()).unwrap();
-
-        self.stack.push(el.clone());
-        self.nodes[(id as usize)] = Some(el);
-    }
-
-    fn new_event_listener(&mut self, event: &'static str, _scope: ScopeId, _real_id: u64) {
-        let event = wasm_bindgen::intern(event);
-
-        // attach the correct attributes to the element
-        // these will be used by accessing the event's target
-        // This ensures we only ever have one handler attached to the root, but decide
-        // dynamically when we want to call a listener.
-
-        let el = self.stack.top();
-
-        let el = el.dyn_ref::<Element>().unwrap();
-
-        el.set_attribute("dioxus-event", event).unwrap();
-
-        // Register the callback to decode
-
-        if let Some(entry) = self.listeners.get_mut(event) {
-            entry.0 += 1;
-        } else {
-            let trigger = self.sender_callback.clone();
-
-            let c: Box<dyn FnMut(&Event)> = Box::new(move |event: &web_sys::Event| {
-                // "Result" cannot be received from JS
-                // Instead, we just build and immediately execute a closure that returns result
-                match decode_trigger(event) {
-                    Ok(synthetic_event) => {
-                        let target = event.target().unwrap();
-                        if let Some(node) = target.dyn_ref::<HtmlElement>() {
-                            if let Some(name) = node.get_attribute("dioxus-prevent-default") {
-                                if name == synthetic_event.name
-                                    || name.trim_start_matches("on") == synthetic_event.name
-                                {
-                                    log::trace!("Preventing default");
-                                    event.prevent_default();
-                                }
-                            }
-                        }
-
-                        trigger.as_ref()(SchedulerMsg::Event(synthetic_event))
-                    }
-                    Err(e) => log::error!("Error decoding Dioxus event attribute. {:#?}", e),
-                };
-            });
-
-            let handler = Closure::wrap(c);
-
-            self.root
-                .add_event_listener_with_callback(event, (&handler).as_ref().unchecked_ref())
-                .unwrap();
-
-            // Increment the listeners
-            self.listeners.insert(event.into(), (1, handler));
-        }
-    }
-
-    fn remove_event_listener(&mut self, _event: &str, _root: u64) {
-        todo!()
-    }
-
-    fn set_text(&mut self, text: &str, root: u64) {
-        let el = self.nodes[root as usize].as_ref().unwrap();
-        el.set_text_content(Some(text))
-    }
-
-    fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>, root: u64) {
-        let node = self.nodes[root as usize].as_ref().unwrap();
-        if ns == Some("style") {
-            if let Some(el) = node.dyn_ref::<Element>() {
-                let el = el.dyn_ref::<HtmlElement>().unwrap();
-                let style_dc: CssStyleDeclaration = el.style();
-                style_dc.set_property(name, value).unwrap();
-            }
-        } else {
-            let fallback = || {
-                let el = node.dyn_ref::<Element>().unwrap();
-                el.set_attribute(name, value).unwrap()
-            };
-            match name {
-                "dangerous_inner_html" => {
-                    if let Some(el) = node.dyn_ref::<Element>() {
-                        el.set_inner_html(value);
-                    }
-                }
-                "value" => {
-                    if let Some(input) = node.dyn_ref::<HtmlInputElement>() {
-                        /*
-                        if the attribute being set is the same as the value of the input, then don't bother setting it.
-                        This is used in controlled components to keep the cursor in the right spot.
-
-                        this logic should be moved into the virtualdom since we have the notion of "volatile"
-                        */
-                        if input.value() != value {
-                            input.set_value(value);
-                        }
-                    } else if let Some(node) = node.dyn_ref::<HtmlTextAreaElement>() {
-                        if name == "value" {
-                            node.set_value(value);
-                        }
-                    } else {
-                        fallback();
-                    }
-                }
-                "checked" => {
-                    if let Some(input) = node.dyn_ref::<HtmlInputElement>() {
-                        match value {
-                            "true" => input.set_checked(true),
-                            "false" => input.set_checked(false),
-                            _ => fallback(),
-                        }
-                    } else {
-                        fallback();
-                    }
-                }
-                "selected" => {
-                    if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
-                        node.set_selected(true);
-                    } else {
-                        fallback();
-                    }
-                }
-                _ => {
-                    // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
-                    if value == "false" {
-                        if let Some(el) = node.dyn_ref::<Element>() {
-                            match name {
-                                "allowfullscreen"
-                                | "allowpaymentrequest"
-                                | "async"
-                                | "autofocus"
-                                | "autoplay"
-                                | "checked"
-                                | "controls"
-                                | "default"
-                                | "defer"
-                                | "disabled"
-                                | "formnovalidate"
-                                | "hidden"
-                                | "ismap"
-                                | "itemscope"
-                                | "loop"
-                                | "multiple"
-                                | "muted"
-                                | "nomodule"
-                                | "novalidate"
-                                | "open"
-                                | "playsinline"
-                                | "readonly"
-                                | "required"
-                                | "reversed"
-                                | "selected"
-                                | "truespeed" => {
-                                    let _ = el.remove_attribute(name);
-                                }
-                                _ => {
-                                    let _ = el.set_attribute(name, value);
-                                }
-                            };
-                        }
-                    } else {
-                        fallback();
-                    }
-                }
-            }
-        }
-    }
-
-    fn remove_attribute(&mut self, name: &str, root: u64) {
-        let node = self.nodes[root as usize].as_ref().unwrap();
-        if let Some(node) = node.dyn_ref::<web_sys::Element>() {
-            node.remove_attribute(name).unwrap();
-        }
-        if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
-            // Some attributes are "volatile" and don't work through `removeAttribute`.
-            if name == "value" {
-                node.set_value("");
-            }
-            if name == "checked" {
-                node.set_checked(false);
-            }
-        }
-
-        if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
-            if name == "selected" {
-                node.set_selected(true);
-            }
-        }
-    }
-
-    fn insert_after(&mut self, n: u32, root: u64) {
-        let old = self.nodes[root as usize].as_ref().unwrap();
-
-        let arr: js_sys::Array = self
-            .stack
-            .list
-            .drain((self.stack.list.len() - n as usize)..)
-            .collect();
-
-        if let Some(el) = old.dyn_ref::<Element>() {
-            el.after_with_node(&arr).unwrap();
-        } else if let Some(el) = old.dyn_ref::<web_sys::CharacterData>() {
-            el.after_with_node(&arr).unwrap();
-        } else if let Some(el) = old.dyn_ref::<web_sys::DocumentType>() {
-            el.after_with_node(&arr).unwrap();
-        }
-    }
-
-    fn insert_before(&mut self, n: u32, root: u64) {
-        let anchor = self.nodes[root as usize].as_ref().unwrap();
-
-        if n == 1 {
-            let before = self.stack.pop();
-
-            anchor
-                .parent_node()
-                .unwrap()
-                .insert_before(&before, Some(&anchor))
-                .unwrap();
-        } else {
-            let arr: js_sys::Array = self
-                .stack
-                .list
-                .drain((self.stack.list.len() - n as usize)..)
-                .collect();
-
-            if let Some(el) = anchor.dyn_ref::<Element>() {
-                el.before_with_node(&arr).unwrap();
-            } else if let Some(el) = anchor.dyn_ref::<web_sys::CharacterData>() {
-                el.before_with_node(&arr).unwrap();
-            } else if let Some(el) = anchor.dyn_ref::<web_sys::DocumentType>() {
-                el.before_with_node(&arr).unwrap();
-            }
-        }
-    }
-}
-
-#[derive(Debug, Default)]
-struct Stack {
-    list: Vec<Node>,
-}
-
-impl Stack {
-    #[inline]
-    fn with_capacity(cap: usize) -> Self {
-        Stack {
-            list: Vec::with_capacity(cap),
-        }
-    }
-
-    #[inline]
-    fn push(&mut self, node: Node) {
-        self.list.push(node);
-    }
-
-    #[inline]
-    fn pop(&mut self) -> Node {
-        self.list.pop().unwrap()
-    }
-
-    fn top(&self) -> &Node {
-        match self.list.last() {
-            Some(a) => a,
-            None => panic!("Called 'top' of an empty stack, make sure to push the root first"),
-        }
-    }
-}
-
-pub struct DioxusWebsysEvent(web_sys::Event);
-
-// safety: currently the web is not multithreaded and our VirtualDom exists on the same thread
-unsafe impl Send for DioxusWebsysEvent {}
-unsafe impl Sync for DioxusWebsysEvent {}
-
-// todo: some of these events are being casted to the wrong event type.
-// We need tests that simulate clicks/etc and make sure every event type works.
-fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send + Sync> {
-    use dioxus_html::on::*;
-    use dioxus_html::KeyCode;
-
-    match event.type_().as_str() {
-        "copy" | "cut" | "paste" => Arc::new(ClipboardData {}),
-        "compositionend" | "compositionstart" | "compositionupdate" => {
-            let evt: &web_sys::CompositionEvent = event.dyn_ref().unwrap();
-            Arc::new(CompositionData {
-                data: evt.data().unwrap_or_default(),
-            })
-        }
-        "keydown" | "keypress" | "keyup" => {
-            let evt: &web_sys::KeyboardEvent = event.dyn_ref().unwrap();
-            Arc::new(KeyboardData {
-                alt_key: evt.alt_key(),
-                char_code: evt.char_code(),
-                key: evt.key(),
-                key_code: KeyCode::from_raw_code(evt.key_code() as u8),
-                ctrl_key: evt.ctrl_key(),
-                location: evt.location() as usize,
-                meta_key: evt.meta_key(),
-                repeat: evt.repeat(),
-                shift_key: evt.shift_key(),
-                which: evt.which() as usize,
-            })
-        }
-        "focus" | "blur" => Arc::new(FocusData {}),
-
-        // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
-        // don't have a good solution with the serialized event problem
-        "change" | "input" | "invalid" | "reset" | "submit" => {
-            let evt: &web_sys::Event = event.dyn_ref().unwrap();
-
-            let target: web_sys::EventTarget = evt.target().unwrap();
-            let value: String = (&target)
-                .dyn_ref()
-                .map(|input: &web_sys::HtmlInputElement| {
-                    // todo: special case more input types
-                    match input.type_().as_str() {
-                        "checkbox" => {
-                           match input.checked() {
-                                true => "true".to_string(),
-                                false => "false".to_string(),
-                            }
-                        },
-                        _ => {
-                            input.value()
-                        }
-                    }
-                })
-                .or_else(|| {
-                    target
-                        .dyn_ref()
-                        .map(|input: &web_sys::HtmlTextAreaElement| input.value())
-                })
-                // select elements are NOT input events - because - why woudn't they be??
-                .or_else(|| {
-                    target
-                        .dyn_ref()
-                        .map(|input: &web_sys::HtmlSelectElement| input.value())
-                })
-                .or_else(|| {
-                    target
-                        .dyn_ref::<web_sys::HtmlElement>()
-                        .unwrap()
-                        .text_content()
-                })
-                .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
-
-            Arc::new(FormData { value })
-        }
-        "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
-        | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
-        | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
-            let evt: &web_sys::MouseEvent = event.dyn_ref().unwrap();
-            Arc::new(MouseData {
-                alt_key: evt.alt_key(),
-                button: evt.button(),
-                buttons: evt.buttons(),
-                client_x: evt.client_x(),
-                client_y: evt.client_y(),
-                ctrl_key: evt.ctrl_key(),
-                meta_key: evt.meta_key(),
-                screen_x: evt.screen_x(),
-                screen_y: evt.screen_y(),
-                shift_key: evt.shift_key(),
-                page_x: evt.page_x(),
-                page_y: evt.page_y(),
-            })
-        }
-        "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
-        | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
-            let evt: &web_sys::PointerEvent = event.dyn_ref().unwrap();
-            Arc::new(PointerData {
-                alt_key: evt.alt_key(),
-                button: evt.button(),
-                buttons: evt.buttons(),
-                client_x: evt.client_x(),
-                client_y: evt.client_y(),
-                ctrl_key: evt.ctrl_key(),
-                meta_key: evt.meta_key(),
-                page_x: evt.page_x(),
-                page_y: evt.page_y(),
-                screen_x: evt.screen_x(),
-                screen_y: evt.screen_y(),
-                shift_key: evt.shift_key(),
-                pointer_id: evt.pointer_id(),
-                width: evt.width(),
-                height: evt.height(),
-                pressure: evt.pressure(),
-                tangential_pressure: evt.tangential_pressure(),
-                tilt_x: evt.tilt_x(),
-                tilt_y: evt.tilt_y(),
-                twist: evt.twist(),
-                pointer_type: evt.pointer_type(),
-                is_primary: evt.is_primary(),
-                // get_modifier_state: evt.get_modifier_state(),
-            })
-        }
-        "select" => Arc::new(SelectionData {}),
-        "touchcancel" | "touchend" | "touchmove" | "touchstart" => {
-            let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap();
-            Arc::new(TouchData {
-                alt_key: evt.alt_key(),
-                ctrl_key: evt.ctrl_key(),
-                meta_key: evt.meta_key(),
-                shift_key: evt.shift_key(),
-            })
-        }
-        "scroll" => Arc::new(()),
-        "wheel" => {
-            let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap();
-            Arc::new(WheelData {
-                delta_x: evt.delta_x(),
-                delta_y: evt.delta_y(),
-                delta_z: evt.delta_z(),
-                delta_mode: evt.delta_mode(),
-            })
-        }
-        "animationstart" | "animationend" | "animationiteration" => {
-            let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap();
-            Arc::new(AnimationData {
-                elapsed_time: evt.elapsed_time(),
-                animation_name: evt.animation_name(),
-                pseudo_element: evt.pseudo_element(),
-            })
-        }
-        "transitionend" => {
-            let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap();
-            Arc::new(TransitionData {
-                elapsed_time: evt.elapsed_time(),
-                property_name: evt.property_name(),
-                pseudo_element: evt.pseudo_element(),
-            })
-        }
-        "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
-        | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
-        | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
-        | "timeupdate" | "volumechange" | "waiting" => Arc::new(MediaData {}),
-        "toggle" => Arc::new(ToggleData {}),
-        _ => Arc::new(()),
-    }
-}
-
-/// This function decodes a websys event and produces an EventTrigger
-/// With the websys implementation, we attach a unique key to the nodes
-fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
-    use anyhow::Context;
-
-    let target = event
-        .target()
-        .expect("missing target")
-        .dyn_into::<Element>()
-        .expect("not a valid element");
-
-    let typ = event.type_();
-
-    let element_id = target
-        .get_attribute("dioxus-id")
-        .context("Could not find element id on event target")?
-        .parse()?;
-
-    Ok(UserEvent {
-        name: event_name_from_typ(&typ),
-        data: virtual_event_from_websys_event(event.clone()),
-        element: Some(ElementId(element_id)),
-        scope_id: None,
-        priority: dioxus_core::EventPriority::Medium,
-    })
-}
-
-pub(crate) fn load_document() -> Document {
-    web_sys::window()
-        .expect("should have access to the Window")
-        .document()
-        .expect("should have access to the Document")
-}
-
-fn event_name_from_typ(typ: &str) -> &'static str {
-    match typ {
-        "copy" => "copy",
-        "cut" => "cut",
-        "paste" => "paste",
-        "compositionend" => "compositionend",
-        "compositionstart" => "compositionstart",
-        "compositionupdate" => "compositionupdate",
-        "keydown" => "keydown",
-        "keypress" => "keypress",
-        "keyup" => "keyup",
-        "focus" => "focus",
-        "blur" => "blur",
-        "change" => "change",
-        "input" => "input",
-        "invalid" => "invalid",
-        "reset" => "reset",
-        "submit" => "submit",
-        "click" => "click",
-        "contextmenu" => "contextmenu",
-        "doubleclick" => "doubleclick",
-        "drag" => "drag",
-        "dragend" => "dragend",
-        "dragenter" => "dragenter",
-        "dragexit" => "dragexit",
-        "dragleave" => "dragleave",
-        "dragover" => "dragover",
-        "dragstart" => "dragstart",
-        "drop" => "drop",
-        "mousedown" => "mousedown",
-        "mouseenter" => "mouseenter",
-        "mouseleave" => "mouseleave",
-        "mousemove" => "mousemove",
-        "mouseout" => "mouseout",
-        "mouseover" => "mouseover",
-        "mouseup" => "mouseup",
-        "pointerdown" => "pointerdown",
-        "pointermove" => "pointermove",
-        "pointerup" => "pointerup",
-        "pointercancel" => "pointercancel",
-        "gotpointercapture" => "gotpointercapture",
-        "lostpointercapture" => "lostpointercapture",
-        "pointerenter" => "pointerenter",
-        "pointerleave" => "pointerleave",
-        "pointerover" => "pointerover",
-        "pointerout" => "pointerout",
-        "select" => "select",
-        "touchcancel" => "touchcancel",
-        "touchend" => "touchend",
-        "touchmove" => "touchmove",
-        "touchstart" => "touchstart",
-        "scroll" => "scroll",
-        "wheel" => "wheel",
-        "animationstart" => "animationstart",
-        "animationend" => "animationend",
-        "animationiteration" => "animationiteration",
-        "transitionend" => "transitionend",
-        "abort" => "abort",
-        "canplay" => "canplay",
-        "canplaythrough" => "canplaythrough",
-        "durationchange" => "durationchange",
-        "emptied" => "emptied",
-        "encrypted" => "encrypted",
-        "ended" => "ended",
-        "error" => "error",
-        "loadeddata" => "loadeddata",
-        "loadedmetadata" => "loadedmetadata",
-        "loadstart" => "loadstart",
-        "pause" => "pause",
-        "play" => "play",
-        "playing" => "playing",
-        "progress" => "progress",
-        "ratechange" => "ratechange",
-        "seeked" => "seeked",
-        "seeking" => "seeking",
-        "stalled" => "stalled",
-        "suspend" => "suspend",
-        "timeupdate" => "timeupdate",
-        "volumechange" => "volumechange",
-        "waiting" => "waiting",
-        "toggle" => "toggle",
-        _ => {
-            panic!("unsupported event type")
-        }
-    }
-}
-
-
-//! This module provides a mirror of the VirtualDOM Element Slab using a Vector.
-
-use std::ops::{Index, IndexMut};
-use web_sys::Node;
-
-pub(crate) struct NodeSlab {
-    nodes: Vec<Option<Node>>,
-}
-
-impl NodeSlab {
-    pub fn new(capacity: usize) -> NodeSlab {
-        let nodes = Vec::with_capacity(capacity);
-        NodeSlab { nodes }
-    }
-}
-impl Index<usize> for NodeSlab {
-    type Output = Option<Node>;
-    fn index(&self, index: usize) -> &Self::Output {
-        &self.nodes[index]
-    }
-}
-
-impl IndexMut<usize> for NodeSlab {
-    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
-        if index >= self.nodes.capacity() * 3 {
-            panic!("Trying to mutate an element way too far out of bounds");
-        }
-
-        if index + 1 > self.nodes.len() {
-            self.nodes.resize_with(index + 1, || None);
-        }
-        &mut self.nodes[index]
-    }
-}
-
-
-#[derive(Debug, Default)]
-struct Stack {
-    list: Vec<Node>,
-}
-
-impl Stack {
-    #[inline]
-    fn with_capacity(cap: usize) -> Self {
-        Stack {
-            list: Vec::with_capacity(cap),
-        }
-    }
-
-    #[inline]
-    fn push(&mut self, node: Node) {
-        self.list.push(node);
-    }
-
-    #[inline]
-    fn pop(&mut self) -> Node {
-        self.list.pop().unwrap()
-    }
-
-    fn top(&self) -> &Node {
-        match self.list.last() {
-            Some(a) => a,
-            None => panic!("Called 'top' of an empty stack, make sure to push the root first"),
-        }
-    }
-}

+ 11 - 10
packages/web/src/rehydrate.rs

@@ -1,6 +1,5 @@
 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};
 
@@ -30,14 +29,15 @@ impl WebsysDom {
 
         let mut last_node_was_text = false;
 
-        // Recursively rehydrate the dom from the VirtualDom
-        self.rehydrate_single(
-            &mut nodes,
-            &mut counter,
-            dom,
-            root_node,
-            &mut last_node_was_text,
-        )
+        todo!()
+        // // Recursively rehydrate the dom from the VirtualDom
+        // self.rehydrate_single(
+        //     &mut nodes,
+        //     &mut counter,
+        //     dom,
+        //     root_node,
+        //     &mut last_node_was_text,
+        // )
     }
 
     fn rehydrate_single(
@@ -164,7 +164,8 @@ impl WebsysDom {
             VNode::Component(el) => {
                 let scope = dom.get_scope(el.scope.get().unwrap()).unwrap();
                 let node = scope.root_node();
-                self.rehydrate_single(nodes, place, dom, node, last_node_was_text)?;
+                todo!()
+                // self.rehydrate_single(nodes, place, dom, node, last_node_was_text)?;
             }
             VNode::TemplateRef(_) => todo!(),
         }

+ 1 - 1
packages/web/tests/hydrate.rs

@@ -50,7 +50,7 @@ fn rehydrates() {
 
     let mut dom = VirtualDom::new(app);
     let _ = dom.rebuild();
-    let out = dioxus_ssr::render_vdom_cfg(&dom, |c| c.pre_render(true));
+    let out = dioxus_ssr::render_vdom_cfg(&dom, Default::default());
 
     window()
         .unwrap()