Bläddra i källkod

feat: wire up event delegator for webview

Jonathan Kelley 3 år sedan
förälder
incheckning
7dfe89c958

+ 3 - 4
examples/basic.rs

@@ -5,10 +5,9 @@ fn main() {
         use dioxus_elements::{GlobalAttributes, SvgAttributes};
         __cx.element(
             dioxus_elements::button,
-            __cx.bump()
-                .alloc([dioxus::events::on::onclick(__cx, move |_| {})]),
-            __cx.bump().alloc([]),
-            __cx.bump().alloc([]),
+            [dioxus::events::on::onclick(__cx, move |_| {})],
+            [],
+            [],
             None,
         )
     });

+ 2 - 1
examples/calculator.rs

@@ -5,6 +5,7 @@
 // use dioxus::prelude::*;
 
 fn main() {
+    env_logger::init();
     dioxus::desktop::launch(App, |cfg| cfg);
 }
 
@@ -23,7 +24,7 @@ const App: FC<()> = |cx| {
     let operator = use_state(cx, || None as Option<Operator>);
     let display_value = use_state(cx, || "".to_string());
 
-    let clear_display = display_value.eq("0");
+    let clear_display = display_value == "0";
     let clear_text = if clear_display { "C" } else { "AC" };
 
     let input_digit = move |num: u8| display_value.get_mut().push_str(num.to_string().as_str());

+ 1 - 0
examples/model.rs

@@ -20,6 +20,7 @@ use dioxus::prelude::*;
 
 const STYLE: &str = include_str!("./assets/calculator.css");
 fn main() {
+    env_logger::init();
     dioxus::desktop::launch(App, |cfg| cfg.with_title("Calculator Demo"))
         .expect("failed to launch dioxus app");
 }

+ 1 - 0
examples/webview.rs

@@ -12,6 +12,7 @@
 use dioxus::prelude::*;
 
 fn main() {
+    env_logger::init();
     dioxus::desktop::launch(App, |c| c);
 }
 

+ 5 - 7
packages/core-macro/src/htm.rs

@@ -13,8 +13,6 @@
 //!
 //!
 
-use crate::util::is_valid_svg_tag;
-
 use {
     proc_macro::TokenStream,
     proc_macro2::{Span, TokenStream as TokenStream2},
@@ -163,11 +161,11 @@ impl ToTokens for ToToksCtx<&Element> {
             self.recurse(attr).to_tokens(tokens);
         }
 
-        if is_valid_svg_tag(&name.to_string()) {
-            tokens.append_all(quote! {
-                .namespace(Some("http://www.w3.org/2000/svg"))
-            });
-        }
+        // if is_valid_svg_tag(&name.to_string()) {
+        //     tokens.append_all(quote! {
+        //         .namespace(Some("http://www.w3.org/2000/svg"))
+        //     });
+        // }
 
         match &self.inner.children {
             MaybeExpr::Expr(expr) => tokens.append_all(quote! {

+ 0 - 1
packages/core-macro/src/lib.rs

@@ -7,7 +7,6 @@ pub(crate) mod htm;
 pub(crate) mod ifmt;
 pub(crate) mod props;
 pub(crate) mod rsx;
-pub(crate) mod util;
 
 #[proc_macro]
 pub fn format_args_f(input: TokenStream) -> TokenStream {

+ 0 - 1
packages/core-macro/src/rsx/body.rs

@@ -1,4 +1,3 @@
-use crate::util::is_valid_tag;
 use proc_macro2::TokenStream as TokenStream2;
 use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{

+ 1 - 2
packages/core-macro/src/rsx/element.rs

@@ -273,8 +273,7 @@ fn parse_rsx_element_field(
 
     let ty: AttrType = match name_str.as_str() {
         // short circuit early if style is using the special syntax
-        "style" if stream.peek(Token![:]) => {
-            stream.parse::<Token![:]>().unwrap();
+        "style" if stream.peek(token::Brace) => {
             let inner;
             syn::braced!(inner in stream);
 

+ 0 - 179
packages/core-macro/src/util.rs

@@ -1,179 +0,0 @@
-// use lazy_static::lazy_static;
-use once_cell::sync::Lazy;
-use std::collections::hash_set::HashSet;
-use syn::{parse::ParseBuffer, Expr};
-
-pub fn try_parse_bracketed(stream: &ParseBuffer) -> syn::Result<Expr> {
-    let content;
-    syn::braced!(content in stream);
-    content.parse()
-}
-
-/// rsx! and html! macros support the html namespace as well as svg namespace
-static HTML_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
-    [
-        "a",
-        "abbr",
-        "address",
-        "area",
-        "article",
-        "aside",
-        "audio",
-        "b",
-        "base",
-        "bdi",
-        "bdo",
-        "big",
-        "blockquote",
-        "body",
-        "br",
-        "button",
-        "canvas",
-        "caption",
-        "cite",
-        "code",
-        "col",
-        "colgroup",
-        "command",
-        "data",
-        "datalist",
-        "dd",
-        "del",
-        "details",
-        "dfn",
-        "dialog",
-        "div",
-        "dl",
-        "dt",
-        "em",
-        "embed",
-        "fieldset",
-        "figcaption",
-        "figure",
-        "footer",
-        "form",
-        "h1",
-        "h2",
-        "h3",
-        "h4",
-        "h5",
-        "h6",
-        "head",
-        "header",
-        "hr",
-        "html",
-        "i",
-        "iframe",
-        "img",
-        "input",
-        "ins",
-        "kbd",
-        "keygen",
-        "label",
-        "legend",
-        "li",
-        "link",
-        "main",
-        "map",
-        "mark",
-        "menu",
-        "menuitem",
-        "meta",
-        "meter",
-        "nav",
-        "noscript",
-        "object",
-        "ol",
-        "optgroup",
-        "option",
-        "output",
-        "p",
-        "param",
-        "picture",
-        "pre",
-        "progress",
-        "q",
-        "rp",
-        "rt",
-        "ruby",
-        "s",
-        "samp",
-        "script",
-        "section",
-        "select",
-        "small",
-        "source",
-        "span",
-        "strong",
-        "style",
-        "sub",
-        "summary",
-        "sup",
-        "table",
-        "tbody",
-        "td",
-        "textarea",
-        "tfoot",
-        "th",
-        "thead",
-        "time",
-        "title",
-        "tr",
-        "track",
-        "u",
-        "ul",
-        "var",
-        "video",
-        "wbr",
-    ]
-    .iter()
-    .cloned()
-    .collect()
-});
-
-static SVG_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
-    [
-        // SVTG
-        "svg", "path", "g", "text",
-    ]
-    .iter()
-    .cloned()
-    .collect()
-});
-
-// these tags are reserved by dioxus for any reason
-// They might not all be used
-static RESERVED_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
-    [
-        // a fragment
-        "fragment",
-    ]
-    .iter()
-    .cloned()
-    .collect()
-});
-
-/// Whether or not this tag is valid
-///
-/// ```
-/// use html_validation::is_valid_tag;
-///
-/// assert_eq!(is_valid_tag("br"), true);
-///
-/// assert_eq!(is_valid_tag("random"), false);
-/// ```
-pub fn is_valid_tag(tag: &str) -> bool {
-    is_valid_html_tag(tag) || is_valid_svg_tag(tag) || is_valid_reserved_tag(tag)
-}
-
-pub fn is_valid_html_tag(tag: &str) -> bool {
-    HTML_TAGS.contains(tag)
-}
-
-pub fn is_valid_svg_tag(tag: &str) -> bool {
-    SVG_TAGS.contains(tag)
-}
-
-pub fn is_valid_reserved_tag(tag: &str) -> bool {
-    RESERVED_TAGS.contains(tag)
-}

+ 1 - 2
packages/core/src/arena.rs

@@ -20,8 +20,7 @@ pub struct ElementId(pub usize);
 
 impl ElementId {
     pub fn as_u64(self) -> u64 {
-        todo!()
-        // self.0.as_ffi()
+        self.0 as u64
     }
 }
 

+ 6 - 5
packages/core/src/diff.rs

@@ -85,7 +85,7 @@ impl<'r, 'b> DiffMachine<'r, 'b> {
     pub fn get_scope_mut(&mut self, id: &ScopeId) -> Option<&'b mut Scope> {
         // ensure we haven't seen this scope before
         // if we have, then we're trying to alias it, which is not allowed
-        debug_assert!(!self.seen_nodes.contains(id));
+        // debug_assert!(!self.seen_nodes.contains(id));
         unsafe { self.vdom.get_scope_mut(*id) }
     }
     pub fn get_scope(&mut self, id: &ScopeId) -> Option<&'b Scope> {
@@ -120,10 +120,11 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
     //
     // each function call assumes the stack is fresh (empty).
     pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
-        let root = old_node
-            .dom_id
-            .get()
-            .expect("Should not be diffing old nodes that were never assigned");
+        // currently busted for components - need to fid
+        let root = old_node.dom_id.get().expect(&format!(
+            "Should not be diffing old nodes that were never assigned, {:#?}",
+            old_node
+        ));
 
         match (&old_node.kind, &new_node.kind) {
             // Handle the "sane" cases first.

+ 6 - 1
packages/core/src/events.rs

@@ -125,6 +125,8 @@ pub enum VirtualEvent {
         hook_idx: usize,
         domnode: Rc<Cell<Option<ElementId>>>,
     },
+    // TOOD: make garbage collection its own dedicated event
+    // GarbageCollection {}
 }
 
 pub mod on {
@@ -183,6 +185,7 @@ pub mod on {
                         where F: FnMut($wrapper) + 'a
                     {
                         let bump = &c.bump();
+
                         let cb: &mut dyn FnMut(VirtualEvent) = bump.alloc(move |evt: VirtualEvent| match evt {
                             VirtualEvent::$wrapper(event) => callback(event),
                             _ => unreachable!("Downcasted VirtualEvent to wrong event type - this is an internal bug!")
@@ -190,8 +193,10 @@ pub mod on {
 
                         let callback: BumpBox<dyn FnMut(VirtualEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
 
+                        let event_name = stringify!($name);
+                        let shortname: &'static str = &event_name[2..];
                         Listener {
-                            event: stringify!($name),
+                            event: shortname,
                             mounted_node: Cell::new(None),
                             scope: c.scope.our_arena_idx,
                             callback: RefCell::new(callback),

+ 3 - 3
packages/core/src/lib.rs

@@ -10,9 +10,9 @@
 //!
 
 pub use crate::innerlude::{
-    format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventTrigger,
-    LazyNodes, NodeFactory, Properties, RealDom, ScopeId, VNode, VNodeKind, VirtualDom,
-    VirtualEvent, FC,
+    format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority,
+    EventTrigger, LazyNodes, NodeFactory, Properties, RealDom, ScopeId, VNode, VNodeKind,
+    VirtualDom, VirtualEvent, FC,
 };
 
 pub mod prelude {

+ 1 - 2
packages/core/src/nodes.rs

@@ -249,13 +249,12 @@ impl<'a> NodeFactory<'a> {
 
         // We take the references directly from the bump arena
         //
-        //
         // TODO: this code shouldn't necessarily be here of all places
         // It would make more sense to do this in diffing
 
         let mut queue = self.scope.listeners.borrow_mut();
         for listener in listeners.iter() {
-            let long_listener: &Listener<'static> = unsafe { std::mem::transmute(listener) };
+            let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
             queue.push(long_listener as *const _)
         }
 

+ 2 - 0
packages/core/src/scope.rs

@@ -108,6 +108,8 @@ impl Scope {
         let next_frame = self.frames.prev_frame_mut();
         next_frame.bump.reset();
 
+        // make sure we call the drop implementation on all the listeners
+        // this is important to not leak memory
         self.listeners.borrow_mut().clear();
 
         unsafe { self.hooks.reset() };

+ 3 - 3
packages/core/src/virtual_dom.rs

@@ -258,10 +258,10 @@ impl VirtualDom {
     // but the guarantees provide a safe, fast, and efficient abstraction for the VirtualDOM updating framework.
     //
     // A good project would be to remove all unsafe from this crate and move the unsafety into safer abstractions.
-    pub async fn progress_with_event<'s>(
+    pub async fn progress_with_event<'a, 's>(
         &'s mut self,
-        realdom: &'s mut dyn RealDom<'s>,
-        edits: &mut Vec<DomEdit<'s>>,
+        realdom: &'a mut dyn RealDom<'s>,
+        edits: &'a mut Vec<DomEdit<'s>>,
     ) -> Result<()> {
         let trigger = self.triggers.borrow_mut().pop().expect("failed");
 

+ 1 - 1
packages/desktop/Cargo.toml

@@ -20,13 +20,13 @@ log = "0.4.13"
 fern = { version = "0.6.0", features = ["colored"] }
 html-escape = "0.2.9"
 wry = "0.11.0"
+async-std = { version = "1.9.0", features = ["attributes"] }
 
 
 [dev-dependencies]
 dioxus-html = { path = "../html" }
 tide = "0.15.0"
 tide-websockets = "0.3.0"
-async-std = { version = "1.9.0", features = ["attributes"] }
 
 # thiserror = "1.0.23"
 # log = "0.4.13"

+ 85 - 0
packages/desktop/src/events.rs

@@ -0,0 +1,85 @@
+//! Convert a serialized event to an event Trigger
+//!
+
+use std::rc::Rc;
+
+use dioxus_core::{
+    events::{
+        on::{MouseEvent, MouseEventInner},
+        VirtualEvent,
+    },
+    ElementId, EventPriority, EventTrigger, ScopeId,
+};
+
+#[derive(serde::Serialize, serde::Deserialize)]
+struct ImEvent {
+    event: String,
+    mounted_dom_id: u64,
+    scope: u64,
+}
+pub fn trigger_from_serialized(val: serde_json::Value) -> EventTrigger {
+    let mut data: Vec<ImEvent> = serde_json::from_value(val).unwrap();
+    let data = data.drain(..).next().unwrap();
+
+    let event = VirtualEvent::MouseEvent(MouseEvent(Rc::new(WebviewMouseEvent)));
+    let scope = ScopeId(data.scope as usize);
+    let mounted_dom_id = Some(ElementId(data.mounted_dom_id as usize));
+    let priority = EventPriority::High;
+    EventTrigger::new(event, scope, mounted_dom_id, priority)
+}
+
+#[derive(Debug)]
+struct WebviewMouseEvent;
+impl MouseEventInner for WebviewMouseEvent {
+    fn alt_key(&self) -> bool {
+        todo!()
+    }
+
+    fn button(&self) -> i16 {
+        todo!()
+    }
+
+    fn buttons(&self) -> u16 {
+        todo!()
+    }
+
+    fn client_x(&self) -> i32 {
+        todo!()
+    }
+
+    fn client_y(&self) -> i32 {
+        todo!()
+    }
+
+    fn ctrl_key(&self) -> bool {
+        todo!()
+    }
+
+    fn meta_key(&self) -> bool {
+        todo!()
+    }
+
+    fn page_x(&self) -> i32 {
+        todo!()
+    }
+
+    fn page_y(&self) -> i32 {
+        todo!()
+    }
+
+    fn screen_x(&self) -> i32 {
+        todo!()
+    }
+
+    fn screen_y(&self) -> i32 {
+        todo!()
+    }
+
+    fn shift_key(&self) -> bool {
+        todo!()
+    }
+
+    fn get_modifier_state(&self, key_code: &str) -> bool {
+        todo!()
+    }
+}

+ 108 - 45
packages/desktop/src/index.html

@@ -5,10 +5,13 @@
     <script>
         class Interpreter {
             constructor(root) {
+                this.root = root;
                 this.stack = [root];
-                this.listeners = {};
+                this.listeners = {
+                    "onclick": {}
+                };
                 this.lastNodeWasText = false;
-                this.nodes = [];
+                this.nodes = [root, root, root, root];
             }
 
             top() {
@@ -18,66 +21,125 @@
             pop() {
                 return this.stack.pop();
             }
-        }
 
-        class OPTABLE {
-            PushRoot(self, edit) {
-                const id = edit.root;
-                const node = self.nodes[id];
-                self.stack.push(node);
+            PushRoot(edit) {
+                const id = edit.id;
+                const node = this.nodes[id];
+                console.log("pushing root ", node, "with id", id);
+                this.stack.push(node);
             }
-            AppendChildren(self, edit) {
-                let root = self.stack[self.stack.length - (edit.many + 1)];
+
+            AppendChildren(edit) {
+                let root = this.stack[this.stack.length - (edit.many + 1)];
                 for (let i = 0; i < edit.many; i++) {
                     console.log("popping ", i, edit.many);
-                    let node = self.pop();
+                    let node = this.pop();
                     root.appendChild(node);
                 }
             }
-            ReplaceWith(self, edit) {
 
-                let root = self.stack[self.stack.length - (edit.many + 1)];
+            ReplaceWith(edit) {
+
+                let root = this.stack[this.stack.length - (edit.many + 1)];
                 let els = [];
 
                 for (let i = 0; i < edit.many; i++) {
-                    els.push(self.pop());
+                    els.push(this.pop());
                 }
 
                 root.replaceWith(...els);
             }
-            Remove(self, edit) {
-                const node = self.stack.pop();
+
+            Remove(edit) {
+                const node = this.stack.pop();
                 node.remove();
             }
-            RemoveAllChildren(self, edit) {}
-            CreateTextNode(self, edit) {
-                self.stack.push(document.createTextNode(edit.text));
+
+            RemoveAllChildren(edit) {}
+
+            CreateTextNode(edit) {
+                const node = document.createTextNode(edit.text);
+                this.nodes[edit.id] = node;
+                this.stack.push(node);
             }
-            CreateElement(self, edit) {
+
+            CreateElement(edit) {
                 const tagName = edit.tag;
+                const el = document.createElement(tagName);
+                this.nodes[edit.id] = el;
                 console.log(`creating element: `, edit);
-                self.stack.push(document.createElement(tagName));
+                this.stack.push(el);
             }
-            CreateElementNs(self, edit) {
+
+            CreateElementNs(edit) {
                 const tagName = edit.tag;
                 console.log(`creating namespaced element: `, edit);
-                self.stack.push(document.createElementNS(edit.ns, edit.tag));
+                this.stack.push(document.createElementNS(edit.ns, edit.tag));
             }
-            CreatePlaceholder(self, edit) {
-                const a = `self.stack.push(document.createElement("pre"))`;
-                self.stack.push(document.createComment("vroot"));
+
+            CreatePlaceholder(edit) {
+                const a = `this.stack.push(document.createElement(" pre"))`;
+                this.stack.push(document.createComment("vroot"));
             }
-            NewEventListener(self, edit) {}
-            RemoveEventListener(self, edit) {}
-            SetText(self, edit) {
-                self.top().textContent = edit.text;
+
+            NewEventListener(edit) {
+                const element_id = edit.element_id;
+                const event_name = edit.event_name;
+                const mounted_node_id = edit.mounted_node_id;
+                const scope = edit.scope;
+
+                const element = this.top();
+                element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`);
+
+                console.log("listener map is", this.listeners);
+                if (this.listeners[event_name] === undefined) {
+                    console.log("adding listener!");
+                    this.listeners[event_name] = "bla";
+                    this.root.addEventListener(event_name, (event) => {
+                        const target = event.target;
+                        const type = event.type;
+                        const val = target.getAttribute(`dioxus-event-${event_name}`);
+                        const fields = val.split(".");
+                        const scope_id = parseInt(fields[0]);
+                        const real_id = parseInt(fields[1]);
+
+                        console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`);
+
+                        rpc.call('user_event', {
+                            event: event_name,
+                            scope: scope_id,
+                            mounted_dom_id: real_id,
+                        }).then((reply) => {
+                            console.log(reply);
+                            this.stack.push(this.root);
+
+                            for (let x = 0; x < reply.length; x++) {
+                                let edit = reply[x];
+                                console.log(edit);
+
+                                let f = this[edit.type];
+                                f.call(this, edit);
+                            }
+
+                            console.log("initiated");
+                        }).catch((err) => {
+                            console.log("failed to initiate", err);
+                        });
+                    });
+                }
             }
-            SetAttribute(self, edit) {
+
+            RemoveEventListener(edit) {}
+
+            SetText(edit) {
+                this.top().textContent = edit.text;
+            }
+
+            SetAttribute(edit) {
                 const name = edit.field;
                 const value = edit.value;
                 const ns = edit.ns;
-
-                const node = self.top(self.stack);
+                const node = this.top(this.stack);
                 if (ns == "style") {
                     node.style[name] = value;
                 } else if (ns !== undefined) {
@@ -85,36 +147,35 @@
                 } else {
                     node.setAttribute(name, value);
                 }
-
-                if ((name === "value", self)) {
+                if (name === "value") {
                     node.value = value;
                 }
-                if ((name === "checked", self)) {
+                if (name === "checked") {
                     node.checked = true;
                 }
-                if ((name === "selected", self)) {
+                if (name === "selected") {
                     node.selected = true;
                 }
             }
-            RemoveAttribute(self, edit) {
+            RemoveAttribute(edit) {
                 const name = edit.field;
-                const node = self.top(self.stack);
-
+                const node = this.top(this.stack);
                 node.removeAttribute(name);
 
-                if ((name === "value", self)) {
+                if (name === "value") {
                     node.value = null;
                 }
-                if ((name === "checked", self)) {
+                if (name === "checked") {
                     node.checked = false;
                 }
-                if ((name === "selected", self)) {
+                if (name === "selected") {
                     node.selected = false;
                 }
             }
         }
 
-        const op_table = new OPTABLE();
+
+
 
         async function initialize() {
             const reply = await rpc.call('initiate');
@@ -124,7 +185,9 @@
             for (let x = 0; x < reply.length; x++) {
                 let edit = reply[x];
                 console.log(edit);
-                op_table[edit.type](interpreter, edit);
+
+                let f = interpreter[edit.type];
+                f.call(interpreter, edit);
             }
 
             console.log("stack completed: ", interpreter.stack);

+ 34 - 13
packages/desktop/src/lib.rs

@@ -17,6 +17,8 @@ use wry::{
 
 mod dom;
 mod escape;
+mod events;
+use events::*;
 
 static HTML_CONTENT: &'static str = include_str!("./index.html");
 
@@ -59,6 +61,7 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
         user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
         redits: Option<Vec<DomEdit<'static>>>,
     ) -> anyhow::Result<()> {
+        log::info!("hello edits");
         let event_loop = EventLoop::new();
 
         let window = user_builder(WindowBuilder::new()).build(&event_loop)?;
@@ -101,24 +104,42 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
                         Some(RpcResponse::new_result(req.id.take(), Some(edits)))
                     }
                     "user_event" => {
-                        let mut lock = vdom.write().unwrap();
-                        let mut reg_lock = registry.write().unwrap();
+                        log::debug!("User event received");
 
-                        // Create the thin wrapper around the registry to collect the edits into
-                        let mut real = dom::WebviewDom::new(reg_lock.take().unwrap());
+                        let registry = registry.clone();
+                        let vdom = vdom.clone();
+                        let response = async_std::task::block_on(async move {
+                            let mut lock = vdom.write().unwrap();
+                            let mut reg_lock = registry.write().unwrap();
+
+                            // a deserialized event
+                            let data = req.params.unwrap();
+                            log::debug!("Data: {:#?}", data);
+                            let event = trigger_from_serialized(data);
+
+                            lock.queue_event(event);
 
-                        // Serialize the edit stream
-                        let edits = {
+                            // Create the thin wrapper around the registry to collect the edits into
+                            let mut real = dom::WebviewDom::new(reg_lock.take().unwrap());
+
+                            // Serialize the edit stream
+                            //
                             let mut edits = Vec::new();
-                            lock.rebuild(&mut real, &mut edits).unwrap();
-                            serde_json::to_value(edits).unwrap()
-                        };
+                            lock.progress_with_event(&mut real, &mut edits)
+                                .await
+                                .expect("failed to progress");
+                            let edits = serde_json::to_value(edits).unwrap();
+
+                            // Give back the registry into its slot
+                            *reg_lock = Some(real.consume());
 
-                        // Give back the registry into its slot
-                        *reg_lock = Some(real.consume());
+                            // Return the edits into the webview runtime
+                            Some(RpcResponse::new_result(req.id.take(), Some(edits)))
+                        });
 
-                        // Return the edits into the webview runtime
-                        Some(RpcResponse::new_result(req.id.take(), Some(edits)))
+                        response
+
+                        // spawn a task to clean up the garbage
                     }
                     _ => todo!("this message failed"),
                 }

+ 2 - 2
packages/hooks/src/usestate.rs

@@ -212,8 +212,8 @@ impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseState<'a, T> {
         self.set(self.inner.current_val.div(rhs));
     }
 }
-impl<'a, T: PartialEq<T>> PartialEq<T> for UseState<'a, T> {
-    fn eq(&self, other: &T) -> bool {
+impl<'a, V, T: PartialEq<V>> PartialEq<V> for UseState<'a, T> {
+    fn eq(&self, other: &V) -> bool {
         self.get() == other
     }
 }