Parcourir la source

extend interpreter base class with platform methods

Jonathan Kelley il y a 1 an
Parent
commit
b5447c162f

+ 9 - 0
packages/core/src/runtime.rs

@@ -168,6 +168,15 @@ impl Runtime {
     pub(crate) fn release_flush_lock(&self) {
         self.flush_lock.take();
     }
+
+    /// Dispatch an event against the current runtime
+    ///
+    /// This won't do any diffing or anything, just calling event listeners syncronously
+    /// We expose this via the runtime so that preventDefault, stopPropagation, and other event methods can be called
+    /// while the event is still active.
+    pub fn dispatch_event(&self) {
+        todo!()
+    }
 }
 
 /// A guard for a new runtime. This must be used to override the current runtime when importing components from a dynamic library that has it's own runtime.

+ 23 - 0
packages/desktop/js/prevent_file_upload.js

@@ -0,0 +1,23 @@
+// Prevent file inputs from opening the file dialog on click
+  let inputs = document.querySelectorAll("input");
+  for (let input of inputs) {
+    if (!input.getAttribute("data-dioxus-file-listener")) {
+      // prevent file inputs from opening the file dialog on click
+      const type = input.getAttribute("type");
+      if (type === "file") {
+        input.setAttribute("data-dioxus-file-listener", true);
+        input.addEventListener("click", (event) => {
+          let target = event.target;
+          let target_id = find_real_id(target);
+          if (target_id !== null) {
+            const send = (event_name) => {
+              const message = window.interpreter.serializeIpcMessage("file_diolog", { accept: target.getAttribute("accept"), directory: target.getAttribute("webkitdirectory") === "true", multiple: target.hasAttribute("multiple"), target: parseInt(target_id), bubbles: event_bubbles(event_name), event: event_name });
+              window.ipc.postMessage(message);
+            };
+            send("change&input");
+          }
+          event.preventDefault();
+        });
+      }
+    }
+  }

+ 12 - 36
packages/desktop/src/protocol.rs

@@ -1,46 +1,20 @@
 use crate::{assets::*, edits::EditQueue};
-use dioxus_interpreter_js::binary_protocol::SLEDGEHAMMER_JS;
+use dioxus_interpreter_js::unified_bindings::SLEDGEHAMMER_JS;
 use std::path::{Path, PathBuf};
 use wry::{
     http::{status::StatusCode, Request, Response},
     RequestAsyncResponder, Result,
 };
 
-fn handle_edits_code() -> String {
-    const EDITS_PATH: &str = {
-        #[cfg(any(target_os = "android", target_os = "windows"))]
-        {
-            "http://dioxus.index.html/edits"
-        }
-        #[cfg(not(any(target_os = "android", target_os = "windows")))]
-        {
-            "dioxus://index.html/edits"
-        }
-    };
+#[cfg(any(target_os = "android", target_os = "windows"))]
+const EDITS_PATH: &str = "http://dioxus.index.html/edits";
 
-    let prevent_file_upload = r#"// Prevent file inputs from opening the file dialog on click
-    let inputs = document.querySelectorAll("input");
-    for (let input of inputs) {
-      if (!input.getAttribute("data-dioxus-file-listener")) {
-        // prevent file inputs from opening the file dialog on click
-        const type = input.getAttribute("type");
-        if (type === "file") {
-          input.setAttribute("data-dioxus-file-listener", true);
-          input.addEventListener("click", (event) => {
-            let target = event.target;
-            let target_id = find_real_id(target);
-            if (target_id !== null) {
-              const send = (event_name) => {
-                const message = window.interpreter.serializeIpcMessage("file_diolog", { accept: target.getAttribute("accept"), directory: target.getAttribute("webkitdirectory") === "true", multiple: target.hasAttribute("multiple"), target: parseInt(target_id), bubbles: event_bubbles(event_name), event: event_name });
-                window.ipc.postMessage(message);
-              };
-              send("change&input");
-            }
-            event.preventDefault();
-          });
-        }
-      }
-    }"#;
+#[cfg(not(any(target_os = "android", target_os = "windows")))]
+const EDITS_PATH: &str = "dioxus://index.html/edits";
+
+const PREVENT_FILE_UPLOAD: &str = include_str!("../js/prevent_file_upload.js");
+
+fn handle_edits_code() -> String {
     let polling_request = format!(
         r#"// Poll for requests
     window.interpreter.wait_for_request = (headless) => {{
@@ -62,10 +36,12 @@ fn handle_edits_code() -> String {
           }})
     }}"#
     );
+
     let mut interpreter = SLEDGEHAMMER_JS
-        .replace("/*POST_HANDLE_EDITS*/", prevent_file_upload)
+        .replace("/*POST_HANDLE_EDITS*/", PREVENT_FILE_UPLOAD)
         .replace("export", "")
         + &polling_request;
+
     while let Some(import_start) = interpreter.find("import") {
         let import_end = interpreter[import_start..]
             .find(|c| c == ';' || c == '\n')

+ 1 - 0
packages/interpreter/.gitignore

@@ -0,0 +1 @@
+gen2/

+ 7 - 0
packages/interpreter/NOTES.md

@@ -0,0 +1,7 @@
+# Notes on the web implementation
+
+Here's some useful resources if you ever need to splunk into the intricacies of how events are handled in HTML:
+
+
+- Not all event handlers are sync: https://w3c.github.io/uievents/#sync-async
+- Some attributes are truthy: https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364

+ 10 - 0
packages/interpreter/README.md

@@ -25,6 +25,16 @@
 
 This crate features bindings for the web and sledgehammer for increased performance.
 
+## Architecture
+
+We use TypeScript to write the bindings and a very simple build.rs to convert them to javascript, minify them, and glue them into the rest of the project.
+
+Not every snippet of JS will be used, so we split out the snippets from the core interpreter.
+
+In theory, we *could* use Rust in the browser to do everything these bindings are doing. In reality, we want to stick with JS to skip the need for a WASM build step when running the LiveView and WebView renderers. We also want to use JS to prevent diverging behavior of things like canceling events, uploading files, and collecting form inputs. These details are tough to ensure 1:1 compatibility when implementing them in two languages.
+
+If you want to contribute to the bindings, you'll need to have the typescript compiler installed on your machine, accessible via `tsc`.
+
 ## Contributing
 
 - Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).

+ 1 - 0
packages/interpreter/a.txt

@@ -0,0 +1 @@
+pub const SLEDGEHAMMER_JS: &str = "let m,p,ls,d,t,op,i,e,z,metaflags;\n            const namespace = [];\n                    let namespace_cache_hit, namespace_cache_idx;\n                    function get_namespace() {\n                        namespace_cache_idx = u8buf[u8bufp++];\n                        if(namespace_cache_idx & 128){\n                            namespace_cache_hit=s.substring(sp,sp+=u8buf[u8bufp++]);\n                            namespace[namespace_cache_idx&4294967167]=namespace_cache_hit;\n                            return namespace_cache_hit;\n                        }\n                        else{\n                            return namespace[namespace_cache_idx&4294967167];\n                        }\n                    }let u8buf,u8bufp;const attr = [];\n                    let attr_cache_hit, attr_cache_idx;\n                    function get_attr() {\n                        attr_cache_idx = u8buf[u8bufp++];\n                        if(attr_cache_idx & 128){\n                            attr_cache_hit=s.substring(sp,sp+=u8buf[u8bufp++]);\n                            attr[attr_cache_idx&4294967167]=attr_cache_hit;\n                            return attr_cache_hit;\n                        }\n                        else{\n                            return attr[attr_cache_idx&4294967167];\n                        }\n                    }const evt = [];\n                    let evt_cache_hit, evt_cache_idx;\n                    function get_evt() {\n                        evt_cache_idx = u8buf[u8bufp++];\n                        if(evt_cache_idx & 128){\n                            evt_cache_hit=s.substring(sp,sp+=u8buf[u8bufp++]);\n                            evt[evt_cache_idx&4294967167]=evt_cache_hit;\n                            return evt_cache_hit;\n                        }\n                        else{\n                            return evt[evt_cache_idx&4294967167];\n                        }\n                    }const el = [];\n                    let el_cache_hit, el_cache_idx;\n                    function get_el() {\n                        el_cache_idx = u8buf[u8bufp++];\n                        if(el_cache_idx & 128){\n                            el_cache_hit=s.substring(sp,sp+=u8buf[u8bufp++]);\n                            el[el_cache_idx&4294967167]=el_cache_hit;\n                            return el_cache_hit;\n                        }\n                        else{\n                            return el[el_cache_idx&4294967167];\n                        }\n                    }let u32buf,u32bufp;let s = \"\";let lsp,sp,sl; let c = new TextDecoder();let u16buf,u16bufp;const ns_cache = [];\n                    let ns_cache_cache_hit, ns_cache_cache_idx;\n                    function get_ns_cache() {\n                        ns_cache_cache_idx = u8buf[u8bufp++];\n                        if(ns_cache_cache_idx & 128){\n                            ns_cache_cache_hit=s.substring(sp,sp+=u8buf[u8bufp++]);\n                            ns_cache[ns_cache_cache_idx&4294967167]=ns_cache_cache_hit;\n                            return ns_cache_cache_hit;\n                        }\n                        else{\n                            return ns_cache[ns_cache_cache_idx&4294967167];\n                        }\n                    }\n            let field,ptr,array,ns,id,value,len,event,many,bubbles,event_name;\n            export  function create(r){\n                d=r;\n            }\n            export  function update_memory(b){\n                m=new DataView(b.buffer)\n            }\n            export  function run(){\n                metaflags=m.getUint32(d,true);\n                if((metaflags>>>6)&1){\n                    ls=m.getUint32(d+6*4,true);\n                }\n                p=ls;\n                if ((metaflags>>>5)&1){\n                t = m.getUint32(d+5*4,true);\n                u8buf=new Uint8Array(m.buffer,t,((m.buffer.byteLength-t)-(m.buffer.byteLength-t)%1)/1);\n            }\n            u8bufp=0;if ((metaflags>>>3)&1){\n                t = m.getUint32(d+3*4,true);\n                u32buf=new Uint32Array(m.buffer,t,((m.buffer.byteLength-t)-(m.buffer.byteLength-t)%4)/4);\n            }\n            u32bufp=0;if (metaflags&1){\n                lsp = m.getUint32(d+1*4,true);\n            }\n            if ((metaflags>>>2)&1) {\n                sl = m.getUint32(d+2*4,true);\n                if ((metaflags>>>1)&1) {\n                    sp = lsp;\n                    s = \"\";\n                    e = sp + ((sl / 4) | 0) * 4;\n                    while (sp < e) {\n                        t = m.getUint32(sp, true);\n                        s += String.fromCharCode(\n                            t & 255,\n                            (t & 65280) >> 8,\n                            (t & 16711680) >> 16,\n                            t >> 24\n                        );\n                        sp += 4;\n                    }\n                    while (sp < lsp + sl) {\n                        s += String.fromCharCode(m.getUint8(sp++));\n                    }\n                } else {\n                    s = c.decode(new DataView(m.buffer, lsp, sl));\n                }\n            }\n            sp=0;if ((metaflags>>>4)&1){\n                t = m.getUint32(d+4*4,true);\n                u16buf=new Uint16Array(m.buffer,t,((m.buffer.byteLength-t)-(m.buffer.byteLength-t)%2)/2);\n            }\n            u16bufp=0;\n                for(;;){\n                    op=m.getUint32(p,true);\n                    p+=4;\n                    z=0;\n                    while(z++<4){\n                        switch(op&255){\n                            case 0:{AppendChildren(root, stack.length-1);}break;case 1:{stack.push(nodes[u32buf[u32bufp++]]);}break;case 2:{AppendChildren(u32buf[u32bufp++], u16buf[u16bufp++]);}break;case 3:{stack.pop();}break;case 4:{root = nodes[u32buf[u32bufp++]]; els = stack.splice(stack.length-u16buf[u16bufp++]); if (root.listening) { listeners.removeAllNonBubbling(root); } root.replaceWith(...els);}break;case 5:{nodes[u32buf[u32bufp++]].after(...stack.splice(stack.length-u16buf[u16bufp++]));}break;case 6:{nodes[u32buf[u32bufp++]].before(...stack.splice(stack.length-u16buf[u16bufp++]));}break;case 7:{node = nodes[u32buf[u32bufp++]]; if (node !== undefined) { if (node.listening) { listeners.removeAllNonBubbling(node); } node.remove(); }}break;case 8:{stack.push(document.createTextNode(s.substring(sp,sp+=u32buf[u32bufp++])));}break;case 9:{node = document.createTextNode(s.substring(sp,sp+=u32buf[u32bufp++])); nodes[u32buf[u32bufp++]] = node; stack.push(node);}break;case 10:{node = document.createElement('pre'); node.hidden = true; stack.push(node); nodes[u32buf[u32bufp++]] = node;}break;case 11:event_name=get_evt();id=u32buf[u32bufp++];bubbles=u8buf[u8bufp++];node = nodes[id]; if(node.listening){node.listening += 1;}else{node.listening = 1;} node.setAttribute('data-dioxus-id', `${id}`); listeners.create(event_name, node, bubbles);break;case 12:{node = nodes[u32buf[u32bufp++]]; node.listening -= 1; node.removeAttribute('data-dioxus-id'); listeners.remove(node, get_evt(), u8buf[u8bufp++]);}break;case 13:{nodes[u32buf[u32bufp++]].textContent = s.substring(sp,sp+=u32buf[u32bufp++]);}break;case 14:{node = nodes[u32buf[u32bufp++]]; setAttributeInner(node, get_attr(), s.substring(sp,sp+=u32buf[u32bufp++]), get_ns_cache());}break;case 15:id=u32buf[u32bufp++];field=get_attr();ns=get_ns_cache();{\n                node = nodes[id];\n                if (!ns) {\n                    switch (field) {\n                        case \"value\":\n                            node.value = \"\";\n                            break;\n                        case \"checked\":\n                            node.checked = false;\n                            break;\n                        case \"selected\":\n                            node.selected = false;\n                            break;\n                        case \"dangerous_inner_html\":\n                            node.innerHTML = \"\";\n                            break;\n                        default:\n                            node.removeAttribute(field);\n                            break;\n                    }\n                } else if (ns == \"style\") {\n                    node.style.removeProperty(name);\n                } else {\n                    node.removeAttributeNS(ns, field);\n                }\n            }break;case 16:{nodes[u32buf[u32bufp++]] = LoadChild(u32buf[u32bufp++], u8buf[u8bufp++]);}break;case 17:ptr=u32buf[u32bufp++];len=u8buf[u8bufp++];value=s.substring(sp,sp+=u32buf[u32bufp++]);id=u32buf[u32bufp++];{\n                node = LoadChild(ptr, len);\n                if (node.nodeType == Node.TEXT_NODE) {\n                    node.textContent = value;\n                } else {\n                    let text = document.createTextNode(value);\n                    node.replaceWith(text);\n                    node = text;\n                }\n                nodes[id] = node;\n            }break;case 18:{els = stack.splice(stack.length - u16buf[u16bufp++]); node = LoadChild(u32buf[u32bufp++], u8buf[u8bufp++]); node.replaceWith(...els);}break;case 19:{node = templates[u16buf[u16bufp++]][u16buf[u16bufp++]].cloneNode(true); nodes[u32buf[u32bufp++]] = node; stack.push(node);}break;case 20:many=u16buf[u16bufp++];{\n            root = stack[stack.length-many-1];\n            els = stack.splice(stack.length-many);\n            for (k = 0; k < many; k++) {\n                root.appendChild(els[k]);\n            }\n        }break;case 21:{setAttributeInner(stack[stack.length-1], get_attr(), s.substring(sp,sp+=u32buf[u32bufp++]), get_ns_cache());}break;case 22:{node = document.createElement('pre'); node.hidden = true; stack.push(node);}break;case 23:{stack.push(document.createElementNS(get_namespace(), get_el()))}break;case 24:{stack.push(document.createElement(get_el()))}break;case 25:{templates[u16buf[u16bufp++]] = stack.splice(stack.length-u16buf[u16bufp++]);}break;case 26:event=get_evt();id=u32buf[u32bufp++];bubbles=u8buf[u8bufp++];\n        bubbles = bubbles == 1;\n        node = nodes[id];\n        if(node.listening){\n            node.listening += 1;\n        } else {\n            node.listening = 1;\n        }\n        node.setAttribute('data-dioxus-id', `${id}`);\n        const event_name = event;\n\n        // if this is a mounted listener, we send the event immediately\n        if (event_name === \"mounted\") {\n            window.ipc.postMessage(\n                window.interpreter.serializeIpcMessage(\"user_event\", {\n                    name: event_name,\n                    element: id,\n                    data: null,\n                    bubbles,\n                })\n            );\n        } else {\n            listeners.create(event_name, node, bubbles, (event) => {\n                handler(event, event_name, bubbles, config);\n            });\n        }break;case 27:{nodes[u32buf[u32bufp++]] = LoadChild((()=>{e=u8bufp+u32buf[u32bufp++];const final_array = u8buf.slice(u8bufp,e);u8bufp=e;return final_array;})());}break;case 28:array=(()=>{e=u8bufp+u32buf[u32bufp++];const final_array = u8buf.slice(u8bufp,e);u8bufp=e;return final_array;})();value=s.substring(sp,sp+=u32buf[u32bufp++]);id=u32buf[u32bufp++];{\n            node = LoadChild(array);\n            if (node.nodeType == Node.TEXT_NODE) {\n                node.textContent = value;\n            } else {\n                let text = document.createTextNode(value);\n                node.replaceWith(text);\n                node = text;\n            }\n            nodes[id] = node;\n        }break;case 29:{els = stack.splice(stack.length - u16buf[u16bufp++]); node = LoadChild((()=>{e=u8bufp+u32buf[u32bufp++];const final_array = u8buf.slice(u8bufp,e);u8bufp=e;return final_array;})()); node.replaceWith(...els);}break;case 30:return true;\n                        }\n                        op>>>=8;\n                    }\n                }\n            }\n            export  function run_from_bytes(bytes){\n                d = 0;\n                update_memory(new Uint8Array(bytes))\n                run()\n            }"

+ 3 - 3
packages/interpreter/build.rs

@@ -8,9 +8,9 @@ fn main() {
     // If any TS changes, re-run the build script
     println!("cargo:rerun-if-changed=src/*.ts");
 
-    for entry in ["common", "form", "interpreter"].iter() {
-        gen_bindings(entry);
-    }
+    // for entry in ["common", "form", "interpreter"].iter() {
+    //     gen_bindings(entry);
+    // }
 }
 
 // okay...... so tsc might fail if the user doesn't have it installed

+ 0 - 80
packages/interpreter/src/common.ts

@@ -1,80 +0,0 @@
-export function setAttributeInner(node, field, value, ns) {
-
-  const name = field;
-  if (ns === "style") {
-    // ????? why do we need to do this
-    if (node.style === undefined) {
-      node.style = {};
-    }
-    node.style[name] = value;
-  } else if (!!ns) {
-    node.setAttributeNS(ns, name, value);
-  } else {
-    switch (name) {
-      case "value":
-        if (value !== node.value) {
-          node.value = value;
-        }
-        break;
-      case "initial_value":
-        node.defaultValue = value;
-        break;
-      case "checked":
-        node.checked = truthy(value);
-        break;
-      case "initial_checked":
-        node.defaultChecked = truthy(value);
-        break;
-      case "selected":
-        node.selected = truthy(value);
-        break;
-      case "initial_selected":
-        node.defaultSelected = truthy(value);
-        break;
-      case "dangerous_inner_html":
-        node.innerHTML = value;
-        break;
-      default:
-        // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
-        if (!truthy(value) && bool_attrs.hasOwnProperty(name)) {
-          node.removeAttribute(name);
-        } else {
-          node.setAttribute(name, value);
-        }
-    }
-  }
-}
-
-const bool_attrs = {
-  allowfullscreen: true,
-  allowpaymentrequest: true,
-  async: true,
-  autofocus: true,
-  autoplay: true,
-  checked: true,
-  controls: true,
-  default: true,
-  defer: true,
-  disabled: true,
-  formnovalidate: true,
-  hidden: true,
-  ismap: true,
-  itemscope: true,
-  loop: true,
-  multiple: true,
-  muted: true,
-  nomodule: true,
-  novalidate: true,
-  open: true,
-  playsinline: true,
-  readonly: true,
-  required: true,
-  reversed: true,
-  selected: true,
-  truespeed: true,
-  webkitdirectory: true,
-};
-
-function truthy(val) {
-  return val === "true" || val === true;
-}

+ 0 - 0
packages/interpreter/src/form.ts → packages/interpreter/src/gen/sledgehammer.js


+ 0 - 792
packages/interpreter/src/interpreter.ts

@@ -1,792 +0,0 @@
-class InterpreterConfig {
-  intercept_link_redirects: boolean;
-
-  constructor(intercept_link_redirects: boolean) {
-    this.intercept_link_redirects = intercept_link_redirects;
-  }
-}
-
-// this handler is only provided on the desktop and liveview implementations since this
-// method is not used by the web implementation
-async function handler(event, name, bubbles, config) {
-  let target = event.target;
-  if (target == null) {
-    return;
-  }
-
-  const realId = find_real_id(target);
-  if (realId === null) {
-    return;
-  }
-
-  prevent_defaults(event, target, config);
-
-  let contents = await serialize_event(event);
-
-  if (
-    target.tagName === "FORM" &&
-    (event.type === "submit" || event.type === "input")
-  ) {
-    const formData = new FormData(target);
-
-    for (let name of formData.keys()) {
-      const fieldType = target.elements[name].type;
-
-      switch (fieldType) {
-        case "select-multiple":
-          contents.values[name] = formData.getAll(name);
-          break;
-
-        // add cases for fieldTypes that can hold multiple values here
-        default:
-          contents.values[name] = formData.get(name);
-          break;
-      }
-    }
-  }
-
-  if (
-    target.tagName === "SELECT" &&
-    event.type === "input"
-  ) {
-    const selectData = target.options;
-    contents.values["options"] = [];
-    for (let i = 0; i < selectData.length; i++) {
-      let option = selectData[i];
-      if (option.selected) {
-        contents.values["options"].push(option.value.toString());
-      }
-    }
-  }
-
-  // TODO: this should be liveview only
-  if (
-    target.tagName === "INPUT" &&
-    (event.type === "change" || event.type === "input")
-  ) {
-    const type = target.getAttribute("type");
-
-    if (type === "file") {
-      async function read_files() {
-        const files = target.files;
-        const file_contents = {};
-
-        for (let i = 0; i < files.length; i++) {
-          const file = files[i];
-
-          file_contents[file.name] = Array.from(
-            new Uint8Array(await file.arrayBuffer())
-          );
-        }
-        let file_engine = {
-          files: file_contents,
-        };
-        contents.files = file_engine;
-
-        if (realId === null) {
-          return;
-        }
-        const message = window.interpreter.serializeIpcMessage("user_event", {
-          name: name,
-          element: parseInt(realId),
-          data: contents,
-          bubbles,
-        });
-        window.ipc.postMessage(message);
-      }
-      read_files();
-      return;
-    }
-  }
-
-  window.ipc.postMessage(
-    window.interpreter.serializeIpcMessage("user_event", {
-      name: name,
-      element: parseInt(realId),
-      data: contents,
-      bubbles,
-    })
-  );
-}
-
-// Do our best to prevent the default action of the event
-// This should:
-// - prevent form submissions from navigating
-// - prevent anchor tags from navigating
-// - prevent buttons from submitting forms
-function prevent_defaults(event, target, config) {
-  let preventDefaultRequests = null;
-
-  // Some events can be triggered on text nodes, which don't have attributes
-  if (target instanceof Element) {
-    preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
-  }
-
-  if (preventDefaultRequests && preventDefaultRequests.includes(`on${event.type}`)) {
-    event.preventDefault();
-  }
-
-  if (event.type === "submit") {
-    event.preventDefault();
-  }
-
-  // Attempt to intercept if the event is a click
-  intercept_form_submit(event, target, config, preventDefaultRequests);
-}
-
-function intercept_form_submit(event, target, config, preventDefaultRequests) {
-  if (event.type !== "click") {
-    return;
-  }
-
-  // todo call prevent default if it's the right type of event
-  if (!config.intercept_link_redirects) {
-    return;
-  }
-
-  // prevent buttons in forms from submitting the form
-  if (target.tagName === "BUTTON" && event.type == "submit") {
-    event.preventDefault();
-  }
-
-  // If the target is an anchor tag, we want to intercept the click too, to prevent the browser from navigating
-  let a_element = target.closest("a");
-
-  if (a_element == null) {
-    return;
-  }
-
-  event.preventDefault();
-
-  let elementShouldPreventDefault =
-    preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
-
-  let aElementShouldPreventDefault = a_element.getAttribute(
-    `dioxus-prevent-default`
-  );
-
-  let linkShouldPreventDefault =
-    aElementShouldPreventDefault &&
-    aElementShouldPreventDefault.includes(`onclick`);
-
-  if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
-    const href = a_element.getAttribute("href");
-    if (href !== "" && href !== null && href !== undefined) {
-      window.ipc.postMessage(
-        window.interpreter.serializeIpcMessage("browser_open", { href })
-      );
-    }
-  }
-}
-
-function find_real_id(target) {
-  let realId = null;
-
-  if (target instanceof Element) {
-    realId = target.getAttribute(`data-dioxus-id`);
-  }
-
-  // walk the tree to find the real element
-  while (realId == null) {
-    // we've reached the root we don't want to send an event
-    if (target.parentElement === null) {
-      return;
-    }
-
-    target = target.parentElement;
-    if (target instanceof Element) {
-      realId = target.getAttribute(`data-dioxus-id`);
-    }
-  }
-
-  return realId;
-}
-
-class ListenerMap {
-  constructor(root) {
-    // bubbling events can listen at the root element
-    this.global = {};
-    // non bubbling events listen at the element the listener was created at
-    this.local = {};
-    this.root = null;
-  }
-
-  create(event_name, element, bubbles, handler) {
-    if (bubbles) {
-      if (this.global[event_name] === undefined) {
-        this.global[event_name] = {};
-        this.global[event_name].active = 1;
-        this.root.addEventListener(event_name, handler);
-      } else {
-        this.global[event_name].active++;
-      }
-    }
-    else {
-      const id = element.getAttribute("data-dioxus-id");
-      if (!this.local[id]) {
-        this.local[id] = {};
-      }
-      element.addEventListener(event_name, handler);
-    }
-  }
-
-  remove(element, event_name, bubbles) {
-    if (bubbles) {
-      this.global[event_name].active--;
-      if (this.global[event_name].active === 0) {
-        this.root.removeEventListener(event_name, this.global[event_name].callback);
-        delete this.global[event_name];
-      }
-    }
-    else {
-      const id = element.getAttribute("data-dioxus-id");
-      delete this.local[id][event_name];
-      if (this.local[id].length === 0) {
-        delete this.local[id];
-      }
-      element.removeEventListener(event_name, this.global[event_name].callback);
-    }
-  }
-
-  removeAllNonBubbling(element) {
-    const id = element.getAttribute("data-dioxus-id");
-    delete this.local[id];
-  }
-}
-function LoadChild(array) {
-  // iterate through each number and get that child
-  node = stack[stack.length - 1];
-
-  for (let i = 0; i < array.length; i++) {
-    end = array[i];
-    for (node = node.firstChild; end > 0; end--) {
-      node = node.nextSibling;
-    }
-  }
-  return node;
-}
-const listeners = new ListenerMap();
-let nodes = [];
-let stack = [];
-let root;
-const templates = {};
-let node, els, end, k;
-
-function AppendChildren(id, many) {
-  root = nodes[id];
-  els = stack.splice(stack.length - many);
-  for (k = 0; k < many; k++) {
-    root.appendChild(els[k]);
-  }
-}
-
-window.interpreter = {}
-
-window.interpreter.initialize = function (root) {
-  nodes = [root];
-  stack = [root];
-  listeners.root = root;
-}
-
-window.interpreter.getClientRect = function (id) {
-  const node = nodes[id];
-  if (!node) {
-    return;
-  }
-  const rect = node.getBoundingClientRect();
-  return {
-    type: "GetClientRect",
-    origin: [rect.x, rect.y],
-    size: [rect.width, rect.height],
-  };
-}
-
-window.interpreter.scrollTo = function (id, behavior) {
-  const node = nodes[id];
-  if (!node) {
-    return false;
-  }
-  node.scrollIntoView({
-    behavior: behavior,
-  });
-  return true;
-}
-
-/// Set the focus on the element
-window.interpreter.setFocus = function (id, focus) {
-  const node = nodes[id];
-  if (!node) {
-    return false;
-  }
-  if (focus) {
-    node.focus();
-  } else {
-    node.blur();
-  }
-  return true;
-}
-
-function get_mouse_data(event: MouseEvent) {
-  const {
-    altKey,
-    button,
-    buttons,
-    clientX,
-    clientY,
-    ctrlKey,
-    metaKey,
-    offsetX,
-    offsetY,
-    pageX,
-    pageY,
-    screenX,
-    screenY,
-    shiftKey,
-  } = event;
-  return {
-    alt_key: altKey,
-    button: button,
-    buttons: buttons,
-    client_x: clientX,
-    client_y: clientY,
-    ctrl_key: ctrlKey,
-    meta_key: metaKey,
-    offset_x: offsetX,
-    offset_y: offsetY,
-    page_x: pageX,
-    page_y: pageY,
-    screen_x: screenX,
-    screen_y: screenY,
-    shift_key: shiftKey,
-  };
-}
-
-async function serialize_event(event) {
-  switch (event.type) {
-    case "copy":
-    case "cut":
-    case "past": {
-      return {};
-    }
-    case "compositionend":
-    case "compositionstart":
-    case "compositionupdate": {
-      let { data } = event;
-      return {
-        data,
-      };
-    }
-    case "keydown":
-    case "keypress":
-    case "keyup": {
-      let {
-        charCode,
-        isComposing,
-        key,
-        altKey,
-        ctrlKey,
-        metaKey,
-        keyCode,
-        shiftKey,
-        location,
-        repeat,
-        which,
-        code,
-      } = event;
-      return {
-        char_code: charCode,
-        is_composing: isComposing,
-        key: key,
-        alt_key: altKey,
-        ctrl_key: ctrlKey,
-        meta_key: metaKey,
-        key_code: keyCode,
-        shift_key: shiftKey,
-        location: location,
-        repeat: repeat,
-        which: which,
-        code,
-      };
-    }
-    case "focus":
-    case "blur": {
-      return {};
-    }
-    case "change": {
-      let target = event.target;
-      let value;
-      if (target.type === "checkbox" || target.type === "radio") {
-        value = target.checked ? "true" : "false";
-      } else {
-        value = target.value ?? target.textContent;
-      }
-      return {
-        value: value,
-        values: {},
-      };
-    }
-    case "input":
-    case "invalid":
-    case "reset":
-    case "submit": {
-      let target = event.target;
-      let value = target.value ?? target.textContent;
-
-      if (target.type === "checkbox") {
-        value = target.checked ? "true" : "false";
-      }
-
-      return {
-        value: value,
-        values: {},
-      };
-    }
-    case "drag":
-    case "dragend":
-    case "dragenter":
-    case "dragexit":
-    case "dragleave":
-    case "dragover":
-    case "dragstart":
-    case "drop": {
-      let files = [];
-
-      if (event.dataTransfer && event.dataTransfer.files) {
-        files = ["a", "b", "c"];
-        // files = await serializeFileList(event.dataTransfer.files);
-      }
-
-      return { mouse: get_mouse_data(event), files };
-    }
-    case "click":
-    case "contextmenu":
-    case "doubleclick":
-    case "dblclick":
-    case "mousedown":
-    case "mouseenter":
-    case "mouseleave":
-    case "mousemove":
-    case "mouseout":
-    case "mouseover":
-    case "mouseup": {
-      return get_mouse_data(event);
-    }
-    case "pointerdown":
-    case "pointermove":
-    case "pointerup":
-    case "pointercancel":
-    case "gotpointercapture":
-    case "lostpointercapture":
-    case "pointerenter":
-    case "pointerleave":
-    case "pointerover":
-    case "pointerout": {
-      const {
-        altKey,
-        button,
-        buttons,
-        clientX,
-        clientY,
-        ctrlKey,
-        metaKey,
-        pageX,
-        pageY,
-        screenX,
-        screenY,
-        shiftKey,
-        pointerId,
-        width,
-        height,
-        pressure,
-        tangentialPressure,
-        tiltX,
-        tiltY,
-        twist,
-        pointerType,
-        isPrimary,
-      } = event;
-      return {
-        alt_key: altKey,
-        button: button,
-        buttons: buttons,
-        client_x: clientX,
-        client_y: clientY,
-        ctrl_key: ctrlKey,
-        meta_key: metaKey,
-        page_x: pageX,
-        page_y: pageY,
-        screen_x: screenX,
-        screen_y: screenY,
-        shift_key: shiftKey,
-        pointer_id: pointerId,
-        width: width,
-        height: height,
-        pressure: pressure,
-        tangential_pressure: tangentialPressure,
-        tilt_x: tiltX,
-        tilt_y: tiltY,
-        twist: twist,
-        pointer_type: pointerType,
-        is_primary: isPrimary,
-      };
-    }
-    case "select": {
-      return {};
-    }
-    case "touchcancel":
-    case "touchend":
-    case "touchmove":
-    case "touchstart": {
-      const { altKey, ctrlKey, metaKey, shiftKey } = event;
-      return {
-        // changed_touches: event.changedTouches,
-        // target_touches: event.targetTouches,
-        // touches: event.touches,
-        alt_key: altKey,
-        ctrl_key: ctrlKey,
-        meta_key: metaKey,
-        shift_key: shiftKey,
-      };
-    }
-    case "scroll": {
-      return {};
-    }
-    case "wheel": {
-      const { deltaX, deltaY, deltaZ, deltaMode } = event;
-      return {
-        delta_x: deltaX,
-        delta_y: deltaY,
-        delta_z: deltaZ,
-        delta_mode: deltaMode,
-      };
-    }
-    case "animationstart":
-    case "animationend":
-    case "animationiteration": {
-      const { animationName, elapsedTime, pseudoElement } = event;
-      return {
-        animation_name: animationName,
-        elapsed_time: elapsedTime,
-        pseudo_element: pseudoElement,
-      };
-    }
-    case "transitionend": {
-      const { propertyName, elapsedTime, pseudoElement } = event;
-      return {
-        property_name: propertyName,
-        elapsed_time: elapsedTime,
-        pseudo_element: pseudoElement,
-      };
-    }
-    case "abort":
-    case "canplay":
-    case "canplaythrough":
-    case "durationchange":
-    case "emptied":
-    case "encrypted":
-    case "ended":
-    case "error":
-    case "loadeddata":
-    case "loadedmetadata":
-    case "loadstart":
-    case "pause":
-    case "play":
-    case "playing":
-    case "progress":
-    case "ratechange":
-    case "seeked":
-    case "seeking":
-    case "stalled":
-    case "suspend":
-    case "timeupdate":
-    case "volumechange":
-    case "waiting": {
-      return {};
-    }
-    case "toggle": {
-      return {};
-    }
-    default: {
-      return {};
-    }
-  }
-}
-window.interpreter.serializeIpcMessage = function (method, params = {}) {
-  return JSON.stringify({ method, params });
-}
-
-function is_element_node(node) {
-  return node.nodeType == 1;
-}
-
-function event_bubbles(event) {
-  switch (event) {
-    case "copy":
-      return true;
-    case "cut":
-      return true;
-    case "paste":
-      return true;
-    case "compositionend":
-      return true;
-    case "compositionstart":
-      return true;
-    case "compositionupdate":
-      return true;
-    case "keydown":
-      return true;
-    case "keypress":
-      return true;
-    case "keyup":
-      return true;
-    case "focus":
-      return false;
-    case "focusout":
-      return true;
-    case "focusin":
-      return true;
-    case "blur":
-      return false;
-    case "change":
-      return true;
-    case "input":
-      return true;
-    case "invalid":
-      return true;
-    case "reset":
-      return true;
-    case "submit":
-      return true;
-    case "click":
-      return true;
-    case "contextmenu":
-      return true;
-    case "doubleclick":
-      return true;
-    case "dblclick":
-      return true;
-    case "drag":
-      return true;
-    case "dragend":
-      return true;
-    case "dragenter":
-      return false;
-    case "dragexit":
-      return false;
-    case "dragleave":
-      return true;
-    case "dragover":
-      return true;
-    case "dragstart":
-      return true;
-    case "drop":
-      return true;
-    case "mousedown":
-      return true;
-    case "mouseenter":
-      return false;
-    case "mouseleave":
-      return false;
-    case "mousemove":
-      return true;
-    case "mouseout":
-      return true;
-    case "scroll":
-      return false;
-    case "mouseover":
-      return true;
-    case "mouseup":
-      return true;
-    case "pointerdown":
-      return true;
-    case "pointermove":
-      return true;
-    case "pointerup":
-      return true;
-    case "pointercancel":
-      return true;
-    case "gotpointercapture":
-      return true;
-    case "lostpointercapture":
-      return true;
-    case "pointerenter":
-      return false;
-    case "pointerleave":
-      return false;
-    case "pointerover":
-      return true;
-    case "pointerout":
-      return true;
-    case "select":
-      return true;
-    case "touchcancel":
-      return true;
-    case "touchend":
-      return true;
-    case "touchmove":
-      return true;
-    case "touchstart":
-      return true;
-    case "wheel":
-      return true;
-    case "abort":
-      return false;
-    case "canplay":
-      return false;
-    case "canplaythrough":
-      return false;
-    case "durationchange":
-      return false;
-    case "emptied":
-      return false;
-    case "encrypted":
-      return true;
-    case "ended":
-      return false;
-    case "error":
-      return false;
-    case "loadeddata":
-    case "loadedmetadata":
-    case "loadstart":
-    case "load":
-      return false;
-    case "pause":
-      return false;
-    case "play":
-      return false;
-    case "playing":
-      return false;
-    case "progress":
-      return false;
-    case "ratechange":
-      return false;
-    case "seeked":
-      return false;
-    case "seeking":
-      return false;
-    case "stalled":
-      return false;
-    case "suspend":
-      return false;
-    case "timeupdate":
-      return false;
-    case "volumechange":
-      return false;
-    case "waiting":
-      return false;
-    case "animationstart":
-      return true;
-    case "animationend":
-      return true;
-    case "animationiteration":
-      return true;
-    case "transitionend":
-      return true;
-    case "toggle":
-      return true;
-    case "mounted":
-      return false;
-  }
-
-  return true;
-}

+ 11 - 6
packages/interpreter/src/lib.rs

@@ -5,14 +5,9 @@
 pub static INTERPRETER_JS: &str = include_str!("./gen/interpreter.js");
 pub static COMMON_JS: &str = include_str!("./gen/common.js");
 
-#[cfg(feature = "sledgehammer")]
-mod sledgehammer_bindings;
-
-#[cfg(feature = "sledgehammer")]
-pub use sledgehammer_bindings::*;
-
 #[cfg(all(feature = "binary-protocol", feature = "sledgehammer"))]
 mod write_native_mutations;
+
 #[cfg(all(feature = "binary-protocol", feature = "sledgehammer"))]
 pub use write_native_mutations::*;
 
@@ -20,8 +15,18 @@ pub use write_native_mutations::*;
 #[cfg(all(feature = "minimal_bindings", feature = "webonly"))]
 pub mod minimal_bindings {
     use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
+
+    /// Some useful snippets that we use to share common functionality between the different platforms we support.
+    ///
+    /// This maintains some sort of consistency between web, desktop, and liveview
     #[wasm_bindgen(module = "/src/gen/common.js")]
     extern "C" {
+        /// Set the attribute of the node
         pub fn setAttributeInner(node: JsValue, name: &str, value: JsValue, ns: Option<&str>);
+
+        pub fn collectFormValues(node: JsValue) -> JsValue;
     }
 }
+
+#[cfg(feature = "sledgehammer")]
+pub mod unified_bindings;

+ 0 - 407
packages/interpreter/src/sledgehammer_bindings.rs

@@ -1,407 +0,0 @@
-#[cfg(feature = "webonly")]
-use js_sys::Function;
-#[cfg(feature = "webonly")]
-use sledgehammer_bindgen::bindgen;
-#[cfg(feature = "webonly")]
-use web_sys::Node;
-
-#[cfg(feature = "webonly")]
-pub const SLEDGEHAMMER_JS: &str = GENERATED_JS;
-
-#[cfg(feature = "webonly")]
-#[bindgen(module)]
-mod js {
-    const JS_FILE: &str = "./src/common.js";
-    const JS: &str = r#"
-    class ListenerMap {
-        constructor(root) {
-            // bubbling events can listen at the root element
-            this.global = {};
-            // non bubbling events listen at the element the listener was created at
-            this.local = {};
-            this.root = null;
-            this.handler = null;
-        }
-
-        create(event_name, element, bubbles) {
-            if (bubbles) {
-                if (this.global[event_name] === undefined) {
-                    this.global[event_name] = {};
-                    this.global[event_name].active = 1;
-                    this.root.addEventListener(event_name, this.handler);
-                } else {
-                    this.global[event_name].active++;
-                }
-            }
-            else {
-                const id = element.getAttribute("data-dioxus-id");
-                if (!this.local[id]) {
-                    this.local[id] = {};
-                }
-                element.addEventListener(event_name, this.handler);
-            }
-        }
-
-        remove(element, event_name, bubbles) {
-            if (bubbles) {
-                this.global[event_name].active--;
-                if (this.global[event_name].active === 0) {
-                    this.root.removeEventListener(event_name, this.global[event_name].callback);
-                    delete this.global[event_name];
-                }
-            }
-            else {
-                const id = element.getAttribute("data-dioxus-id");
-                delete this.local[id][event_name];
-                if (this.local[id].length === 0) {
-                    delete this.local[id];
-                }
-                element.removeEventListener(event_name, this.handler);
-            }
-        }
-
-        removeAllNonBubbling(element) {
-            const id = element.getAttribute("data-dioxus-id");
-            delete this.local[id];
-        }
-    }
-    function LoadChild(ptr, len) {
-        // iterate through each number and get that child
-        node = stack[stack.length - 1];
-        ptr_end = ptr + len;
-        for (; ptr < ptr_end; ptr++) {
-            end = m.getUint8(ptr);
-            for (node = node.firstChild; end > 0; end--) {
-                node = node.nextSibling;
-            }
-        }
-        return node;
-    }
-    const listeners = new ListenerMap();
-    let nodes = [];
-    let stack = [];
-    let root;
-    const templates = {};
-    let node, els, end, ptr_end, k;
-    export function save_template(nodes, tmpl_id) {
-        templates[tmpl_id] = nodes;
-    }
-    export function hydrate(ids) {
-        const hydrateNodes = document.querySelectorAll('[data-node-hydration]');
-        for (let i = 0; i < hydrateNodes.length; i++) {
-            const hydrateNode = hydrateNodes[i];
-            const hydration = hydrateNode.getAttribute('data-node-hydration');
-            const split = hydration.split(',');
-            const id = ids[parseInt(split[0])];
-            nodes[id] = hydrateNode;
-            if (split.length > 1) {
-                hydrateNode.listening = split.length - 1;
-                hydrateNode.setAttribute('data-dioxus-id', id);
-                for (let j = 1; j < split.length; j++) {
-                    const listener = split[j];
-                    const split2 = listener.split(':');
-                    const event_name = split2[0];
-                    const bubbles = split2[1] === '1';
-                    listeners.create(event_name, hydrateNode, bubbles);
-                }
-            }
-        }
-        const treeWalker = document.createTreeWalker(
-            document.body,
-            NodeFilter.SHOW_COMMENT,
-        );
-        let currentNode = treeWalker.nextNode();
-        while (currentNode) {
-            const id = currentNode.textContent;
-            const split = id.split('node-id');
-            if (split.length > 1) {
-                nodes[ids[parseInt(split[1])]] = currentNode.nextSibling;
-            }
-            currentNode = treeWalker.nextNode();
-        }
-    }
-    export function get_node(id) {
-        return nodes[id];
-    }
-    export function initialize(root, handler) {
-        listeners.handler = handler;
-        nodes = [root];
-        stack = [root];
-        listeners.root = root;
-    }
-    function AppendChildren(id, many){
-        root = nodes[id];
-        els = stack.splice(stack.length-many);
-        for (k = 0; k < many; k++) {
-            root.appendChild(els[k]);
-        }
-    }
-    "#;
-
-    extern "C" {
-        #[wasm_bindgen]
-        pub fn save_template(nodes: Vec<Node>, tmpl_id: u16);
-
-        #[wasm_bindgen]
-        pub fn hydrate(ids: Vec<u32>);
-
-        #[wasm_bindgen]
-        pub fn get_node(id: u32) -> Node;
-
-        #[wasm_bindgen]
-        pub fn initialize(root: Node, handler: &Function);
-    }
-
-    fn mount_to_root() {
-        "{AppendChildren(root, stack.length-1);}"
-    }
-    fn push_root(root: u32) {
-        "{stack.push(nodes[$root$]);}"
-    }
-    fn append_children(id: u32, many: u16) {
-        "{AppendChildren($id$, $many$);}"
-    }
-    fn pop_root() {
-        "{stack.pop();}"
-    }
-    fn replace_with(id: u32, n: u16) {
-        "{root = nodes[$id$]; els = stack.splice(stack.length-$n$); if (root.listening) { listeners.removeAllNonBubbling(root); } root.replaceWith(...els);}"
-    }
-    fn insert_after(id: u32, n: u16) {
-        "{nodes[$id$].after(...stack.splice(stack.length-$n$));}"
-    }
-    fn insert_before(id: u32, n: u16) {
-        "{nodes[$id$].before(...stack.splice(stack.length-$n$));}"
-    }
-    fn remove(id: u32) {
-        "{node = nodes[$id$]; if (node !== undefined) { if (node.listening) { listeners.removeAllNonBubbling(node); } node.remove(); }}"
-    }
-    fn create_raw_text(text: &str) {
-        "{stack.push(document.createTextNode($text$));}"
-    }
-    fn create_text_node(text: &str, id: u32) {
-        "{node = document.createTextNode($text$); nodes[$id$] = node; stack.push(node);}"
-    }
-    fn create_placeholder(id: u32) {
-        "{node = document.createElement('pre'); node.hidden = true; stack.push(node); nodes[$id$] = node;}"
-    }
-    fn new_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
-        r#"node = nodes[id]; if(node.listening){node.listening += 1;}else{node.listening = 1;} node.setAttribute('data-dioxus-id', `\${id}`); listeners.create($event_name$, node, $bubbles$);"#
-    }
-    fn remove_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
-        "{node = nodes[$id$]; node.listening -= 1; node.removeAttribute('data-dioxus-id'); listeners.remove(node, $event_name$, $bubbles$);}"
-    }
-    fn set_text(id: u32, text: &str) {
-        "{nodes[$id$].textContent = $text$;}"
-    }
-    fn set_attribute(id: u32, field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) {
-        "{node = nodes[$id$]; setAttributeInner(node, $field$, $value$, $ns$);}"
-    }
-    fn remove_attribute(id: u32, field: &str<u8, attr>, ns: &str<u8, ns_cache>) {
-        r#"{
-            node = nodes[$id$];
-            if (!ns) {
-                switch (field) {
-                    case "value":
-                        node.value = "";
-                        break;
-                    case "checked":
-                        node.checked = false;
-                        break;
-                    case "selected":
-                        node.selected = false;
-                        break;
-                    case "dangerous_inner_html":
-                        node.innerHTML = "";
-                        break;
-                    default:
-                        node.removeAttribute(field);
-                        break;
-                }
-            } else if (ns == "style") {
-                node.style.removeProperty(name);
-            } else {
-                node.removeAttributeNS(ns, field);
-            }
-        }"#
-    }
-    fn assign_id(ptr: u32, len: u8, id: u32) {
-        "{nodes[$id$] = LoadChild($ptr$, $len$);}"
-    }
-    fn hydrate_text(ptr: u32, len: u8, value: &str, id: u32) {
-        r#"{
-            node = LoadChild($ptr$, $len$);
-            if (node.nodeType == Node.TEXT_NODE) {
-                node.textContent = value;
-            } else {
-                let text = document.createTextNode(value);
-                node.replaceWith(text);
-                node = text;
-            }
-            nodes[$id$] = node;
-        }"#
-    }
-    fn replace_placeholder(ptr: u32, len: u8, n: u16) {
-        "{els = stack.splice(stack.length - $n$); node = LoadChild($ptr$, $len$); node.replaceWith(...els);}"
-    }
-    fn load_template(tmpl_id: u16, index: u16, id: u32) {
-        "{node = templates[$tmpl_id$][$index$].cloneNode(true); nodes[$id$] = node; stack.push(node);}"
-    }
-}
-
-#[cfg(feature = "binary-protocol")]
-pub mod binary_protocol {
-    use sledgehammer_bindgen::bindgen;
-    pub const SLEDGEHAMMER_JS: &str = GENERATED_JS;
-
-    #[bindgen]
-    mod protocol_js {
-        const JS_FILE: &str = "./src/interpreter.js";
-        const JS_FILE: &str = "./src/common.js";
-
-        fn mount_to_root() {
-            "{AppendChildren(root, stack.length-1);}"
-        }
-        fn push_root(root: u32) {
-            "{stack.push(nodes[$root$]);}"
-        }
-        fn append_children(id: u32, many: u16) {
-            "{AppendChildren($id$, $many$);}"
-        }
-        fn append_children_to_top(many: u16) {
-            "{
-                root = stack[stack.length-many-1];
-                els = stack.splice(stack.length-many);
-                for (k = 0; k < many; k++) {
-                    root.appendChild(els[k]);
-                }
-            }"
-        }
-        fn pop_root() {
-            "{stack.pop();}"
-        }
-        fn replace_with(id: u32, n: u16) {
-            "{root = nodes[$id$]; els = stack.splice(stack.length-$n$); if (root.listening) { listeners.removeAllNonBubbling(root); } root.replaceWith(...els);}"
-        }
-        fn insert_after(id: u32, n: u16) {
-            "{nodes[$id$].after(...stack.splice(stack.length-$n$));}"
-        }
-        fn insert_before(id: u32, n: u16) {
-            "{nodes[$id$].before(...stack.splice(stack.length-$n$));}"
-        }
-        fn remove(id: u32) {
-            "{node = nodes[$id$]; if (node !== undefined) { if (node.listening) { listeners.removeAllNonBubbling(node); } node.remove(); }}"
-        }
-        fn create_raw_text(text: &str) {
-            "{stack.push(document.createTextNode($text$));}"
-        }
-        fn create_text_node(text: &str, id: u32) {
-            "{node = document.createTextNode($text$); nodes[$id$] = node; stack.push(node);}"
-        }
-        fn create_element(element: &'static str<u8, el>) {
-            "{stack.push(document.createElement($element$))}"
-        }
-        fn create_element_ns(element: &'static str<u8, el>, ns: &'static str<u8, namespace>) {
-            "{stack.push(document.createElementNS($ns$, $element$))}"
-        }
-        fn create_placeholder(id: u32) {
-            "{node = document.createElement('pre'); node.hidden = true; stack.push(node); nodes[$id$] = node;}"
-        }
-        fn add_placeholder() {
-            "{node = document.createElement('pre'); node.hidden = true; stack.push(node);}"
-        }
-        fn new_event_listener(event: &str<u8, evt>, id: u32, bubbles: u8) {
-            r#"
-            bubbles = bubbles == 1;
-            node = nodes[id];
-            if(node.listening){
-                node.listening += 1;
-            } else {
-                node.listening = 1;
-            }
-            node.setAttribute('data-dioxus-id', `\${id}`);
-            const event_name = $event$;
-
-            // if this is a mounted listener, we send the event immediately
-            if (event_name === "mounted") {
-                window.ipc.postMessage(
-                    window.interpreter.serializeIpcMessage("user_event", {
-                        name: event_name,
-                        element: id,
-                        data: null,
-                        bubbles,
-                    })
-                );
-            } else {
-                listeners.create(event_name, node, bubbles, (event) => {
-                    handler(event, event_name, bubbles, config);
-                });
-            }"#
-        }
-        fn remove_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
-            "{node = nodes[$id$]; node.listening -= 1; node.removeAttribute('data-dioxus-id'); listeners.remove(node, $event_name$, $bubbles$);}"
-        }
-        fn set_text(id: u32, text: &str) {
-            "{nodes[$id$].textContent = $text$;}"
-        }
-        fn set_attribute(id: u32, field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) {
-            "{node = nodes[$id$]; setAttributeInner(node, $field$, $value$, $ns$);}"
-        }
-        fn set_top_attribute(field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) {
-            "{setAttributeInner(stack[stack.length-1], $field$, $value$, $ns$);}"
-        }
-        fn remove_attribute(id: u32, field: &str<u8, attr>, ns: &str<u8, ns_cache>) {
-            r#"{
-                node = nodes[$id$];
-                if (!ns) {
-                    switch (field) {
-                        case "value":
-                            node.value = "";
-                            break;
-                        case "checked":
-                            node.checked = false;
-                            break;
-                        case "selected":
-                            node.selected = false;
-                            break;
-                        case "dangerous_inner_html":
-                            node.innerHTML = "";
-                            break;
-                        default:
-                            node.removeAttribute(field);
-                            break;
-                    }
-                } else if (ns == "style") {
-                    node.style.removeProperty(name);
-                } else {
-                    node.removeAttributeNS(ns, field);
-                }
-            }"#
-        }
-        fn assign_id(array: &[u8], id: u32) {
-            "{nodes[$id$] = LoadChild($array$);}"
-        }
-        fn hydrate_text(array: &[u8], value: &str, id: u32) {
-            r#"{
-                node = LoadChild($array$);
-                if (node.nodeType == Node.TEXT_NODE) {
-                    node.textContent = value;
-                } else {
-                    let text = document.createTextNode(value);
-                    node.replaceWith(text);
-                    node = text;
-                }
-                nodes[$id$] = node;
-            }"#
-        }
-        fn replace_placeholder(array: &[u8], n: u16) {
-            "{els = stack.splice(stack.length - $n$); node = LoadChild($array$); node.replaceWith(...els);}"
-        }
-        fn load_template(tmpl_id: u16, index: u16, id: u32) {
-            "{node = templates[$tmpl_id$][$index$].cloneNode(true); nodes[$id$] = node; stack.push(node);}"
-        }
-        fn add_templates(tmpl_id: u16, len: u16) {
-            "{templates[$tmpl_id$] = stack.splice(stack.length-$len$);}"
-        }
-    }
-}

+ 3 - 0
packages/interpreter/src/ts/form.ts

@@ -0,0 +1,3 @@
+function retrieveFormValues() {
+
+}

+ 82 - 0
packages/interpreter/src/ts/interpreter_core.ts

@@ -0,0 +1,82 @@
+// The root interpreter class that holds state about the mapping between DOM and VirtualDom
+// This always lives in the JS side of things, and is extended by the native and web interpreters
+
+// todo: we want to make these vars that the interpreter uses to be isolated so we can have multiple interpreters
+export let m: DataView; // p, ls, d, t, op, i, e, z, metaflags;
+
+export class Interpreter {
+  // non bubbling events listen at the element the listener was created at
+  global: {
+    [key: string]: { active: number, callback: EventListener }
+  };
+  // bubbling events can listen at the root element
+  local: {
+    [key: string]: {
+      [key: string]: EventListener
+    }
+  };
+  root: Element;
+  handler: EventListener;
+  nodes: Element[];
+  stack: Element[];
+  templates: {
+    [key: string]: Element[]
+  };
+
+  constructor(root: Element, handler: EventListener) {
+    this.root = root;
+    this.nodes = [root];
+    this.stack = [root];
+    this.global = {};
+    this.local = {};
+    this.handler = handler;
+  }
+
+  createListener(event_name: string, element: Element, bubbles: boolean) {
+    if (bubbles) {
+      if (this.global[event_name] === undefined) {
+        this.global[event_name] = { active: 1, callback: this.handler };
+        this.root.addEventListener(event_name, this.handler);
+      } else {
+        this.global[event_name].active++;
+      }
+    } else {
+      const id = element.getAttribute("data-dioxus-id");
+      if (!this.local[id]) {
+        this.local[id] = {};
+      }
+      element.addEventListener(event_name, this.handler);
+    }
+  }
+
+  removeListener(element: Element, event_name: string, bubbles: boolean) {
+    if (bubbles) {
+      this.removeBubblingListener(event_name);
+    } else {
+      this.removeNonBubblingListener(element, event_name);
+    }
+  }
+
+  removeBubblingListener(event_name: string) {
+    this.global[event_name].active--;
+    if (this.global[event_name].active === 0) {
+      this.root.removeEventListener(event_name, this.global[event_name].callback);
+      delete this.global[event_name];
+    }
+  }
+
+  removeNonBubblingListener(element: Element, event_name: string) {
+    const id = element.getAttribute("data-dioxus-id");
+    delete this.local[id][event_name];
+    if (Object.keys(this.local[id]).length === 0) {
+      delete this.local[id];
+    }
+    element.removeEventListener(event_name, this.handler);
+  }
+
+  removeAllNonBubblingListeners(element: Element) {
+    const id = element.getAttribute("data-dioxus-id");
+    delete this.local[id];
+  }
+}
+

+ 286 - 0
packages/interpreter/src/ts/interpreter_native.ts

@@ -0,0 +1,286 @@
+// This file provides an extended variant of the interpreter used for desktop and liveview interaction
+//
+//
+
+import { Interpreter } from "./interpreter_core";
+
+export class NativeInterpreter extends Interpreter {
+  intercept_link_redirects: boolean;
+
+  constructor(root: Element) {
+    super(root, (event) => handler(event, this, event.type, true));
+  }
+
+  serializeIpcMessage(method: string, params = {}) {
+    return JSON.stringify({ method, params });
+  }
+
+  scrollTo(id: number, behavior: ScrollBehavior) {
+    const node = this.nodes[id];
+
+    if (!(node instanceof HTMLElement)) {
+      return false;
+    }
+
+    node.scrollIntoView({
+      behavior: behavior,
+    });
+
+    return true;
+  }
+
+  getClientRect(id: number) {
+    const node = this.nodes[id];
+    if (!node) {
+      return;
+    }
+    const rect = node.getBoundingClientRect();
+    return {
+      type: "GetClientRect",
+      origin: [rect.x, rect.y],
+      size: [rect.width, rect.height],
+    };
+  }
+
+  setFocus(id: number, focus: boolean) {
+    const node = this.nodes[id];
+
+    if (!(node instanceof HTMLElement)) {
+      return false;
+    }
+
+    if (focus) {
+      node.focus();
+    } else {
+      node.blur();
+    }
+
+    return true;
+  }
+
+  LoadChild(array: number[]) {
+    // iterate through each number and get that child
+    let node = this.stack[this.stack.length - 1] as Node;
+
+    for (let i = 0; i < array.length; i++) {
+      let end = array[i];
+      for (node = node.firstChild; end > 0; end--) {
+        node = node.nextSibling;
+      }
+    }
+
+    return node;
+  }
+
+  AppendChildren(id: number, many: number) {
+    const root = this.nodes[id];
+    const els = this.stack.splice(this.stack.length - many);
+
+    for (let k = 0; k < many; k++) {
+      root.appendChild(els[k]);
+    }
+  }
+}
+
+
+
+
+function handler(event: Event, interpreter: NativeInterpreter, name: string, bubbles: boolean) {
+  const target = event.target!;
+  const realId = target_id(target)!;
+  let contents = serializeEvent(event);
+
+  if (target instanceof HTMLFormElement && (event.type === "submit" || event.type === "input")) {
+    const formData = new FormData(target);
+
+    for (let name of formData.keys()) {
+      const fieldType = target.elements[name].type;
+
+      switch (fieldType) {
+        case "select-multiple":
+          contents.values[name] = formData.getAll(name);
+          break;
+
+        // add cases for fieldTypes that can hold multiple values here
+        default:
+          contents.values[name] = formData.get(name);
+          break;
+      }
+    }
+  }
+
+  if (target instanceof HTMLSelectElement && (event.type === "input" || event.type === "change")) {
+    const selectData = target.options;
+    contents.values["options"] = [];
+    for (let i = 0; i < selectData.length; i++) {
+      let option = selectData[i];
+      if (option.selected) {
+        contents.values["options"].push(option.value.toString());
+      }
+    }
+  }
+
+  // If there's files to read
+  if (target instanceof HTMLInputElement && (event.type === "change" || event.type === "input")) {
+    if (target.getAttribute("type") === "file") {
+      read_files(target, contents, bubbles, realId, name);
+      return;
+    }
+  }
+
+  prevents_default(event, target);
+
+
+  // Handle the event on the virtualdom and then process whatever its output was
+  let body = {
+    name: name,
+    data: serializeEvent(event),
+    element: parseInt(realId),
+    bubbles,
+  };
+
+  if (waitForVirtualDomPreventDefault(JSON.stringify(body))) {
+    event.preventDefault();
+  }
+}
+
+export function waitForVirtualDomPreventDefault(contents: string): boolean {
+
+
+  // Handle the event on the virtualdom and then process whatever its output was
+  const xhr = new XMLHttpRequest();
+
+  // Serialize the event and send it to the custom protocol in the Rust side of things
+  xhr.timeout = 1000;
+  xhr.open("GET", "/handle/event.please", false);
+  xhr.setRequestHeader("Content-Type", "application/json");
+  xhr.send(contents);
+
+  // Deserialize the response, and then prevent the default/capture the event if the virtualdom wants to
+  return JSON.parse(xhr.responseText).preventDefault;
+}
+
+
+async function read_files(target: HTMLInputElement, contents, bubbles, realId, name) {
+  let files = target.files!;
+  let file_contents: { [name: string]: number[] } = {};
+
+  for (let i = 0; i < files.length; i++) {
+    const file = files[i];
+    file_contents[file.name] = Array.from(
+      new Uint8Array(await file.arrayBuffer())
+    );
+  }
+
+  contents.files = { files: file_contents };
+
+  if (realId === null) {
+    return;
+  }
+
+  const message = window.interpreter.serializeIpcMessage("user_event", {
+    name: name,
+    element: parseInt(realId),
+    data: contents,
+    bubbles,
+  });
+
+  window.ipc.postMessage(message);
+}
+
+
+export function target_id(target: EventTarget): string | null {
+  if (!(target instanceof Element)) {
+    return null;
+  }
+
+  function find_real_id(target: Element): string | null {
+    let realId = target.getAttribute(`data-dioxus-id`);
+
+    // walk the tree to find the real element
+    while (realId == null) {
+      // we've reached the root we don't want to send an event
+      if (target.parentElement === null) {
+        return null;
+      }
+
+      target = target.parentElement;
+      if (target instanceof Element) {
+        realId = target.getAttribute(`data-dioxus-id`);
+      }
+    }
+
+    return realId;
+  }
+
+  return find_real_id(target);
+}
+
+
+
+// This should:
+// - prevent form submissions from navigating
+// - prevent anchor tags from navigating
+// - prevent buttons from submitting forms
+// - let the virtualdom attempt to prevent the event
+preventDefaults(event: Event, target: EventTarget) {
+  let preventDefaultRequests: string | null = null;
+
+  // Some events can be triggered on text nodes, which don't have attributes
+  if (target instanceof Element) {
+    preventDefaultRequests = target.getAttribute(`dioxus-prevent-default`);
+  }
+
+  if (preventDefaultRequests && preventDefaultRequests.includes(`on${event.type}`)) {
+    event.preventDefault();
+  }
+
+  if (event.type === "submit") {
+    event.preventDefault();
+  }
+
+  // Attempt to intercept if the event is a click
+  if (target instanceof Element && event.type === "click") {
+    this.preventFormNavigate(event, target);
+  }
+}
+
+preventFormNavigate(event: Event, target: Element) {
+  // todo call prevent default if it's the right type of event
+  if (!this.intercept_link_redirects) {
+    return;
+  }
+
+  // prevent buttons in forms from submitting the form
+  if (target.tagName === "BUTTON") { //  && event.type == "submit"
+    event.preventDefault();
+  }
+
+  // If the target is an anchor tag, we want to intercept the click too, to prevent the browser from navigating
+  let a_element = target.closest("a");
+  if (a_element == null) {
+    return;
+  }
+
+  event.preventDefault();
+
+  let elementShouldPreventDefault =
+    preventDefaultRequests && preventDefaultRequests.includes(`onclick`);
+
+  let aElementShouldPreventDefault = a_element.getAttribute(
+    `dioxus-prevent-default`
+  );
+
+  let linkShouldPreventDefault =
+    aElementShouldPreventDefault &&
+    aElementShouldPreventDefault.includes(`onclick`);
+
+  if (!elementShouldPreventDefault && !linkShouldPreventDefault) {
+    const href = a_element.getAttribute("href");
+    if (href !== "" && href !== null && href !== undefined) {
+      window.ipc.postMessage(
+        window.interpreter.serializeIpcMessage("browser_open", { href })
+      );
+    }
+  }
+}

+ 83 - 0
packages/interpreter/src/ts/interpreter_web.ts

@@ -0,0 +1,83 @@
+// The JS<->Rust bridge
+// This file is thin, suitable just for the web
+// If you want the more full-featured intrepreter, look at the native interpreter which extends this with additional functionality
+//
+// We're using sledgehammer directly
+
+import { Interpreter, m } from "./interpreter_core";
+
+export class WebInterpreter extends Interpreter {
+  constructor(root: Element, handler: EventListener) {
+    super(root, handler);
+  }
+
+  LoadChild(ptr: number, len: number): Node {
+    // iterate through each number and get that child
+    let node = this.stack[this.stack.length - 1] as Node;
+    let ptr_end = ptr + len;
+
+    for (; ptr < ptr_end; ptr++) {
+      let end = m.getUint8(ptr);
+      for (node = node.firstChild; end > 0; end--) {
+        node = node.nextSibling;
+      }
+    }
+
+    return node;
+  }
+
+  saveTemplate(nodes: Element[], tmpl_id: string) {
+    this.templates[tmpl_id] = nodes;
+  }
+
+  hydrateRoot(ids: { [key: number]: number }) {
+    const hydrateNodes = document.querySelectorAll('[data-node-hydration]');
+
+    for (let i = 0; i < hydrateNodes.length; i++) {
+      const hydrateNode = hydrateNodes[i];
+      const hydration = hydrateNode.getAttribute('data-node-hydration');
+      const split = hydration!.split(',');
+      const id = ids[parseInt(split[0])];
+
+      this.nodes[id] = hydrateNode;
+
+      if (split.length > 1) {
+        // @ts-ignore
+        hydrateNode.listening = split.length - 1;
+        hydrateNode.setAttribute('data-dioxus-id', id.toString());
+        for (let j = 1; j < split.length; j++) {
+          const listener = split[j];
+          const split2 = listener.split(':');
+          const event_name = split2[0];
+          const bubbles = split2[1] === '1';
+          this.createListener(event_name, hydrateNode, bubbles);
+        }
+      }
+    }
+    const treeWalker = document.createTreeWalker(
+      document.body,
+      NodeFilter.SHOW_COMMENT,
+    );
+    let currentNode = treeWalker.nextNode();
+    while (currentNode) {
+      const id = currentNode.textContent!;
+      const split = id.split('node-id');
+      if (split.length > 1) {
+        this.nodes[ids[parseInt(split[1])]] = currentNode.nextSibling as Element;
+      }
+      currentNode = treeWalker.nextNode();
+    }
+  }
+
+  getNode(id: number): Element {
+    return this.nodes[id];
+  }
+
+  appendChildren(id: number, many: number) {
+    const root = this.nodes[id];
+    const els = this.stack.splice(this.stack.length - many);
+    for (let k = 0; k < many; k++) {
+      this.root.appendChild(els[k]);
+    }
+  }
+}

+ 494 - 0
packages/interpreter/src/ts/serialize.ts

@@ -0,0 +1,494 @@
+// Handle serialization of the event data across the IPC boundarytype SerialziedEvent = {};
+
+type SerializedEvent = {
+  values?: { [key: string]: FormDataEntryValue[] } | FormDataEntryValue[];
+  value?: string;
+  [key: string]: any;
+};
+
+function serializeEvent(event: Event): SerializedEvent {
+
+  // copy, cut, paste
+  if (event instanceof KeyboardEvent) {
+    return {
+      char_code: event.charCode,
+      is_composing: event.isComposing,
+      key: event.key,
+      alt_key: event.altKey,
+      ctrl_key: event.ctrlKey,
+      meta_key: event.metaKey,
+      key_code: event.keyCode,
+      shift_key: event.shiftKey,
+      location: event.location,
+      repeat: event.repeat,
+      which: event.which,
+      code: event.code,
+    };
+  }
+
+
+  // "pointerdown" "pointermove" "pointerup" "pointercancel" "gotpointercapture" "lostpointercapture" "pointerenter"
+  // "pointerleave" "pointerover" "pointerout"
+  if (event instanceof PointerEvent) {
+    return {
+      alt_key: event.altKey,
+      button: event.button,
+      buttons: event.buttons,
+      client_x: event.clientX,
+      client_y: event.clientY,
+      ctrl_key: event.ctrlKey,
+      meta_key: event.metaKey,
+      page_x: event.pageX,
+      page_y: event.pageY,
+      screen_x: event.screenX,
+      screen_y: event.screenY,
+      shift_key: event.shiftKey,
+      pointer_id: event.pointerId,
+      width: event.width,
+      height: event.height,
+      pressure: event.pressure,
+      tangential_pressure: event.tangentialPressure,
+      tilt_x: event.tiltX,
+      tilt_y: event.tiltY,
+      twist: event.twist,
+      pointer_type: event.pointerType,
+      is_primary: event.isPrimary,
+    };
+  }
+
+  if (event instanceof MouseEvent) {
+    return {
+      alt_key: event.altKey,
+      button: event.button,
+      buttons: event.buttons,
+      client_x: event.clientX,
+      client_y: event.clientY,
+      ctrl_key: event.ctrlKey,
+      meta_key: event.metaKey,
+      offset_x: event.offsetX,
+      offset_y: event.offsetY,
+      page_x: event.pageX,
+      page_y: event.pageY,
+      screen_x: event.screenX,
+      screen_y: event.screenY,
+      shift_key: event.shiftKey,
+    };
+  }
+
+  if (event instanceof TouchEvent) {
+    return {
+      alt_key: event.altKey,
+      ctrl_key: event.ctrlKey,
+      meta_key: event.metaKey,
+      shift_key: event.shiftKey,
+      changed_touches: event.changedTouches,
+      target_touches: event.targetTouches,
+      touches: event.touches,
+    };
+  }
+
+  if (event instanceof WheelEvent) {
+    return {
+      delta_x: event.deltaX,
+      delta_y: event.deltaY,
+      delta_z: event.deltaZ,
+      delta_mode: event.deltaMode,
+    };
+  }
+
+  if (event instanceof AnimationEvent) {
+    return {
+      animation_name: event.animationName,
+      elapsed_time: event.elapsedTime,
+      pseudo_element: event.pseudoElement,
+    };
+  }
+
+  if (event instanceof TransitionEvent) {
+    return {
+      property_name: event.propertyName,
+      elapsed_time: event.elapsedTime,
+      pseudo_element: event.pseudoElement,
+    };
+  }
+
+  if (event instanceof ClipboardEvent) {
+    return {};
+  }
+
+  if (event instanceof CompositionEvent) {
+    return {
+      data: event.data,
+    };
+  }
+
+  if (event instanceof DragEvent) {
+    return {
+      mouse: {
+        alt_key: event.altKey,
+        ctrl_key: event.ctrlKey,
+        meta_key: event.metaKey,
+        shift_key: event.shiftKey,
+      },
+      files: [],
+    };
+  }
+
+  if (event instanceof FocusEvent) {
+    return {};
+  }
+
+  return {};
+}
+
+function isElementNode(node: Node) {
+  return node.nodeType == 1;
+}
+
+function eventBubbles(eventName: string) {
+  switch (eventName) {
+    case "copy":
+      return true;
+    case "cut":
+      return true;
+    case "paste":
+      return true;
+    case "compositionend":
+      return true;
+    case "compositionstart":
+      return true;
+    case "compositionupdate":
+      return true;
+    case "keydown":
+      return true;
+    case "keypress":
+      return true;
+    case "keyup":
+      return true;
+    case "focus":
+      return false;
+    case "focusout":
+      return true;
+    case "focusin":
+      return true;
+    case "blur":
+      return false;
+    case "change":
+      return true;
+    case "input":
+      return true;
+    case "invalid":
+      return true;
+    case "reset":
+      return true;
+    case "submit":
+      return true;
+    case "click":
+      return true;
+    case "contextmenu":
+      return true;
+    case "doubleclick":
+      return true;
+    case "dblclick":
+      return true;
+    case "drag":
+      return true;
+    case "dragend":
+      return true;
+    case "dragenter":
+      return false;
+    case "dragexit":
+      return false;
+    case "dragleave":
+      return true;
+    case "dragover":
+      return true;
+    case "dragstart":
+      return true;
+    case "drop":
+      return true;
+    case "mousedown":
+      return true;
+    case "mouseenter":
+      return false;
+    case "mouseleave":
+      return false;
+    case "mousemove":
+      return true;
+    case "mouseout":
+      return true;
+    case "scroll":
+      return false;
+    case "mouseover":
+      return true;
+    case "mouseup":
+      return true;
+    case "pointerdown":
+      return true;
+    case "pointermove":
+      return true;
+    case "pointerup":
+      return true;
+    case "pointercancel":
+      return true;
+    case "gotpointercapture":
+      return true;
+    case "lostpointercapture":
+      return true;
+    case "pointerenter":
+      return false;
+    case "pointerleave":
+      return false;
+    case "pointerover":
+      return true;
+    case "pointerout":
+      return true;
+    case "select":
+      return true;
+    case "touchcancel":
+      return true;
+    case "touchend":
+      return true;
+    case "touchmove":
+      return true;
+    case "touchstart":
+      return true;
+    case "wheel":
+      return true;
+    case "abort":
+      return false;
+    case "canplay":
+      return false;
+    case "canplaythrough":
+      return false;
+    case "durationchange":
+      return false;
+    case "emptied":
+      return false;
+    case "encrypted":
+      return true;
+    case "ended":
+      return false;
+    case "error":
+      return false;
+    case "loadeddata":
+    case "loadedmetadata":
+    case "loadstart":
+    case "load":
+      return false;
+    case "pause":
+      return false;
+    case "play":
+      return false;
+    case "playing":
+      return false;
+    case "progress":
+      return false;
+    case "ratechange":
+      return false;
+    case "seeked":
+      return false;
+    case "seeking":
+      return false;
+    case "stalled":
+      return false;
+    case "suspend":
+      return false;
+    case "timeupdate":
+      return false;
+    case "volumechange":
+      return false;
+    case "waiting":
+      return false;
+    case "animationstart":
+      return true;
+    case "animationend":
+      return true;
+    case "animationiteration":
+      return true;
+    case "transitionend":
+      return true;
+    case "toggle":
+      return true;
+    case "mounted":
+      return false;
+  }
+
+  return true;
+}
+
+
+
+// switch (event.type) {
+//   case "copy": case "cut": case "paste": {
+//     return {};
+//   }
+//   case "compositionend":
+//   case "compositionstart":
+//   case "compositionupdate": {
+//     let { data } = event;
+//     return {
+//       data,
+//     };
+//   }
+//   case "keydown": case "keypress": case "keyup": {
+//     if (event instanceof KeyboardEvent) {
+//       return {
+//         char_code: event.charCode,
+//         is_composing: event.isComposing,
+//         key: event.key,
+//         alt_key: event.altKey,
+//         ctrl_key: event.ctrlKey,
+//         meta_key: event.metaKey,
+//         key_code: event.keyCode,
+//         shift_key: event.shiftKey,
+//         location: event.location,
+//         repeat: event.repeat,
+//         which: event.which,
+//         code: event.code,
+//       };
+//     };
+//   }
+//   case "focus":
+//   case "blur": {
+//     return {};
+//   }
+//   case "change": {
+//     let target = event.target;
+//     let value;
+//     if (target.type === "checkbox" || target.type === "radio") {
+//       value = target.checked ? "true" : "false";
+//     } else {
+//       value = target.value ?? target.textContent;
+//     }
+//     return {
+//       value: value,
+//       values: {},
+//     };
+//   }
+//   case "input":
+//   case "invalid":
+//   case "reset":
+//   case "submit": {
+//     let target = event.target;
+//     let value = target.value ?? target.textContent;
+
+//     if (target.type === "checkbox") {
+//       value = target.checked ? "true" : "false";
+//     }
+
+//     return {
+//       value: value,
+//       values: {},
+//     };
+//   }
+//   case "drag":
+//   case "dragend":
+//   case "dragenter":
+//   case "dragexit":
+//   case "dragleave":
+//   case "dragover":
+//   case "dragstart":
+//   case "drop": {
+//     let files = [];
+
+//     if (event.dataTransfer && event.dataTransfer.files) {
+//       files = ["a", "b", "c"];
+//       // files = await serializeFileList(event.dataTransfer.files);
+//     }
+
+//     return { mouse: get_mouse_data(event), files };
+//   }
+//   case "click":
+//   case "contextmenu":
+//   case "doubleclick":
+//   case "dblclick":
+//   case "mousedown":
+//   case "mouseenter":
+//   case "mouseleave":
+//   case "mousemove":
+//   case "mouseout":
+//   case "mouseover":
+//   case "mouseup": {
+//     return get_mouse_data(event);
+//   }
+//   case "select": {
+//     return {};
+//   }
+//   case "touchcancel":
+//   case "touchend":
+//   case "touchmove":
+//   case "touchstart": {
+//     const { altKey, ctrlKey, metaKey, shiftKey } = event;
+//     return {
+//       // changed_touches: event.changedTouches,
+//       // target_touches: event.targetTouches,
+//       // touches: event.touches,
+//       alt_key: altKey,
+//       ctrl_key: ctrlKey,
+//       meta_key: metaKey,
+//       shift_key: shiftKey,
+//     };
+//   }
+//   case "scroll": {
+//     return {};
+//   }
+//   case "wheel": {
+//     const { deltaX, deltaY, deltaZ, deltaMode } = event;
+//     return {
+//       delta_x: deltaX,
+//       delta_y: deltaY,
+//       delta_z: deltaZ,
+//       delta_mode: deltaMode,
+//     };
+//   }
+//   case "animationstart":
+//   case "animationend":
+//   case "animationiteration": {
+//     const { animationName, elapsedTime, pseudoElement } = event;
+//     return {
+//       animation_name: animationName,
+//       elapsed_time: elapsedTime,
+//       pseudo_element: pseudoElement,
+//     };
+//   }
+//   case "transitionend": {
+//     const { propertyName, elapsedTime, pseudoElement } = event;
+//     return {
+//       property_name: propertyName,
+//       elapsed_time: elapsedTime,
+//       pseudo_element: pseudoElement,
+//     };
+//   }
+//   case "abort":
+//   case "canplay":
+//   case "canplaythrough":
+//   case "durationchange":
+//   case "emptied":
+//   case "encrypted":
+//   case "ended":
+//   case "error":
+//   case "loadeddata":
+//   case "loadedmetadata":
+//   case "loadstart":
+//   case "pause":
+//   case "play":
+//   case "playing":
+//   case "progress":
+//   case "ratechange":
+//   case "seeked":
+//   case "seeking":
+//   case "stalled":
+//   case "suspend":
+//   case "timeupdate":
+//   case "volumechange":
+//   case "waiting": {
+//     return {};
+//   }
+//   case "toggle": {
+//     return {};
+//   }
+//   default: {
+//     return {};
+//   }
+// }

+ 105 - 0
packages/interpreter/src/ts/set_attribute.ts

@@ -0,0 +1,105 @@
+// this function should try and stay fast, if possible
+export function setAttributeInner(node: HTMLElement, field: string, value: string, ns: string) {
+  // we support a single namespace by default: style
+  if (ns === "style") {
+    node.style.setProperty(field, value);
+    return;
+  }
+
+  // If there's a namespace, use setAttributeNS (svg, mathml, etc.)
+  if (!!ns) {
+    node.setAttributeNS(ns, field, value);
+    return
+  }
+
+  // A few attributes are need to be set with either boolean values or require some sort of translation
+  switch (field) {
+    case "value":
+      if (value !== node.getAttribute("value")) {
+        node.setAttribute(field, value);
+      }
+      break;
+
+    case "initial_value":
+      node.setAttribute("defaultValue", value);
+      break;
+
+    case "checked":
+      if (node instanceof HTMLInputElement) {
+        node.checked = truthy(value);
+      }
+      break;
+
+    case "initial_checked":
+      if (node instanceof HTMLInputElement) {
+        node.defaultChecked = truthy(value);
+      }
+      break;
+
+    case "selected":
+      if (node instanceof HTMLOptionElement) {
+        node.selected = truthy(value);
+      }
+      break;
+
+    case "initial_selected":
+      if (node instanceof HTMLOptionElement) {
+        node.defaultSelected = truthy(value);
+      }
+      break;
+
+    case "dangerous_inner_html":
+      node.innerHTML = value;
+      break;
+
+    // The presence of a an attribute is enough to set it to true, provided the value is being set to a truthy value
+    // Again, kinda ugly and would prefer this logic to be baked into dioxus-html at compiile time
+    default:
+      // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
+      if (!truthy(value) && isBoolAttr(field)) {
+        node.removeAttribute(field);
+      } else {
+        node.setAttribute(field, value);
+      }
+  }
+}
+
+
+function truthy(val: string | boolean) {
+  return val === "true" || val === true;
+}
+
+function isBoolAttr(field: string): boolean {
+  switch (field) {
+    case "allowfullscreen":
+    case "allowpaymentrequest":
+    case "async":
+    case "autofocus":
+    case "autoplay":
+    case "checked":
+    case "controls":
+    case "default":
+    case "defer":
+    case "disabled":
+    case "formnovalidate":
+    case "hidden":
+    case "ismap":
+    case "itemscope":
+    case "loop":
+    case "multiple":
+    case "muted":
+    case "nomodule":
+    case "novalidate":
+    case "open":
+    case "playsinline":
+    case "readonly":
+    case "required":
+    case "reversed":
+    case "selected":
+    case "truespeed":
+    case "webkitdirectory":
+      return true;
+    default:
+      return false;
+  }
+}

+ 196 - 0
packages/interpreter/src/unified_bindings.rs

@@ -0,0 +1,196 @@
+#[cfg(feature = "webonly")]
+use js_sys::Function;
+
+#[cfg(feature = "webonly")]
+use web_sys::Node;
+
+use sledgehammer_bindgen::bindgen;
+
+pub const SLEDGEHAMMER_JS: &str = GENERATED_JS;
+
+#[bindgen(module)]
+mod js {
+    // Load in the JavaScript file with all the imports
+    const JS_FILE: &str = "./src/gen/interpreter.js";
+
+    // Boot the interpreter and attach us some bindings!
+    const JS: &str = r#""#;
+
+    fn mount_to_root() {
+        "{AppendChildren(root, stack.length-1);}"
+    }
+    fn push_root(root: u32) {
+        "{stack.push(nodes[$root$]);}"
+    }
+    fn append_children(id: u32, many: u16) {
+        "{AppendChildren($id$, $many$);}"
+    }
+
+    fn pop_root() {
+        "{stack.pop();}"
+    }
+    fn replace_with(id: u32, n: u16) {
+        "{root = nodes[$id$]; els = stack.splice(stack.length-$n$); if (root.listening) { listeners.removeAllNonBubbling(root); } root.replaceWith(...els);}"
+    }
+    fn insert_after(id: u32, n: u16) {
+        "{nodes[$id$].after(...stack.splice(stack.length-$n$));}"
+    }
+    fn insert_before(id: u32, n: u16) {
+        "{nodes[$id$].before(...stack.splice(stack.length-$n$));}"
+    }
+    fn remove(id: u32) {
+        "{node = nodes[$id$]; if (node !== undefined) { if (node.listening) { listeners.removeAllNonBubbling(node); } node.remove(); }}"
+    }
+    fn create_raw_text(text: &str) {
+        "{stack.push(document.createTextNode($text$));}"
+    }
+    fn create_text_node(text: &str, id: u32) {
+        "{node = document.createTextNode($text$); nodes[$id$] = node; stack.push(node);}"
+    }
+    fn create_placeholder(id: u32) {
+        "{node = document.createElement('pre'); node.hidden = true; stack.push(node); nodes[$id$] = node;}"
+    }
+    fn new_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
+        r#"node = nodes[id]; if(node.listening){node.listening += 1;}else{node.listening = 1;} node.setAttribute('data-dioxus-id', `\${id}`); listeners.create($event_name$, node, $bubbles$);"#
+    }
+    fn remove_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
+        "{node = nodes[$id$]; node.listening -= 1; node.removeAttribute('data-dioxus-id'); listeners.remove(node, $event_name$, $bubbles$);}"
+    }
+    fn set_text(id: u32, text: &str) {
+        "{nodes[$id$].textContent = $text$;}"
+    }
+    fn set_attribute(id: u32, field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) {
+        "{node = nodes[$id$]; setAttributeInner(node, $field$, $value$, $ns$);}"
+    }
+    fn remove_attribute(id: u32, field: &str<u8, attr>, ns: &str<u8, ns_cache>) {
+        r#"{
+                node = nodes[$id$];
+                if (!ns) {
+                    switch (field) {
+                        case "value":
+                            node.value = "";
+                            break;
+                        case "checked":
+                            node.checked = false;
+                            break;
+                        case "selected":
+                            node.selected = false;
+                            break;
+                        case "dangerous_inner_html":
+                            node.innerHTML = "";
+                            break;
+                        default:
+                            node.removeAttribute(field);
+                            break;
+                    }
+                } else if (ns == "style") {
+                    node.style.removeProperty(name);
+                } else {
+                    node.removeAttributeNS(ns, field);
+                }
+            }"#
+    }
+    fn assign_id(ptr: u32, len: u8, id: u32) {
+        "{nodes[$id$] = LoadChild($ptr$, $len$);}"
+    }
+    fn hydrate_text(ptr: u32, len: u8, value: &str, id: u32) {
+        r#"{
+                node = LoadChild($ptr$, $len$);
+                if (node.nodeType == Node.TEXT_NODE) {
+                    node.textContent = value;
+                } else {
+                    let text = document.createTextNode(value);
+                    node.replaceWith(text);
+                    node = text;
+                }
+                nodes[$id$] = node;
+            }"#
+    }
+    fn replace_placeholder(ptr: u32, len: u8, n: u16) {
+        "{els = stack.splice(stack.length - $n$); node = LoadChild($ptr$, $len$); node.replaceWith(...els);}"
+    }
+    fn load_template(tmpl_id: u16, index: u16, id: u32) {
+        "{node = templates[$tmpl_id$][$index$].cloneNode(true); nodes[$id$] = node; stack.push(node);}"
+    }
+
+    /*
+    Binary protocol methods only!
+
+    These methods let us support binary packing mutations for use on boundaries like desktop where we prefer to send
+    binary data instead of JSON.
+
+    We're using native types in a number of places
+    */
+    fn append_children_to_top(many: u16) {
+        "{
+            root = stack[stack.length-many-1];
+            els = stack.splice(stack.length-many);
+            for (k = 0; k < many; k++) {
+                root.appendChild(els[k]);
+            }
+        }"
+    }
+    fn set_top_attribute(field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) {
+        "{setAttributeInner(stack[stack.length-1], $field$, $value$, $ns$);}"
+    }
+    fn add_placeholder() {
+        "{node = document.createElement('pre'); node.hidden = true; stack.push(node);}"
+    }
+    fn create_element_ns(element: &'static str<u8, el>, ns: &'static str<u8, namespace>) {
+        "{stack.push(document.createElementNS($ns$, $element$))}"
+    }
+    fn create_element(element: &'static str<u8, el>) {
+        "{stack.push(document.createElement($element$))}"
+    }
+    fn add_templates(tmpl_id: u16, len: u16) {
+        "{templates[$tmpl_id$] = stack.splice(stack.length-$len$);}"
+    }
+    fn foreign_event_listener(event: &str<u8, evt>, id: u32, bubbles: u8) {
+        r#"
+        bubbles = bubbles == 1;
+        node = nodes[id];
+        if(node.listening){
+            node.listening += 1;
+        } else {
+            node.listening = 1;
+        }
+        node.setAttribute('data-dioxus-id', `\${id}`);
+        const event_name = $event$;
+
+        // if this is a mounted listener, we send the event immediately
+        if (event_name === "mounted") {
+            window.ipc.postMessage(
+                window.interpreter.serializeIpcMessage("user_event", {
+                    name: event_name,
+                    element: id,
+                    data: null,
+                    bubbles,
+                })
+            );
+        } else {
+            listeners.create(event_name, node, bubbles, (event) => {
+                handler(event, event_name, bubbles, config);
+            });
+        }"#
+    }
+    fn assign_id_ref(array: &[u8], id: u32) {
+        "{nodes[$id$] = LoadChild($array$);}"
+    }
+    fn hydrate_text_ref(array: &[u8], value: &str, id: u32) {
+        r#"{
+            node = LoadChild($array$);
+            if (node.nodeType == Node.TEXT_NODE) {
+                node.textContent = value;
+            } else {
+                let text = document.createTextNode(value);
+                node.replaceWith(text);
+                node = text;
+            }
+            nodes[$id$] = node;
+        }"#
+    }
+
+    fn replace_placeholder_ref(array: &[u8], n: u16) {
+        "{els = stack.splice(stack.length - $n$); node = LoadChild($array$); node.replaceWith(...els);}"
+    }
+}

+ 10 - 8
packages/interpreter/src/write_native_mutations.rs

@@ -1,17 +1,18 @@
-use dioxus_html::event_bubbles;
-
+// use crate::binary_protocol::Channel;
+use crate::unified_bindings::Channel;
 use dioxus_core::{TemplateAttribute, TemplateNode, WriteMutations};
+use dioxus_html::event_bubbles;
 use sledgehammer_utils::rustc_hash::FxHashMap;
 
-use crate::binary_protocol::Channel;
-
 /// The state needed to apply mutations to a channel. This state should be kept across all mutations for the app
 #[derive(Default)]
 pub struct MutationState {
     /// The maximum number of templates that we have registered
     max_template_count: u16,
+
     /// The currently registered templates with the template ids
     templates: FxHashMap<String, u16>,
+
     /// The channel that we are applying mutations to
     channel: Channel,
 }
@@ -100,7 +101,7 @@ impl WriteMutations for MutationState {
     }
 
     fn assign_node_id(&mut self, path: &'static [u8], id: dioxus_core::ElementId) {
-        self.channel.assign_id(path, id.0 as u32);
+        self.channel.assign_id_ref(path, id.0 as u32);
     }
 
     fn create_placeholder(&mut self, id: dioxus_core::ElementId) {
@@ -112,7 +113,7 @@ impl WriteMutations for MutationState {
     }
 
     fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: dioxus_core::ElementId) {
-        self.channel.hydrate_text(path, value, id.0 as u32);
+        self.channel.hydrate_text_ref(path, value, id.0 as u32);
     }
 
     fn load_template(&mut self, name: &'static str, index: usize, id: dioxus_core::ElementId) {
@@ -127,7 +128,7 @@ impl WriteMutations for MutationState {
     }
 
     fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {
-        self.channel.replace_placeholder(path, m as u16);
+        self.channel.replace_placeholder_ref(path, m as u16);
     }
 
     fn insert_nodes_after(&mut self, id: dioxus_core::ElementId, m: usize) {
@@ -181,8 +182,9 @@ impl WriteMutations for MutationState {
     }
 
     fn create_event_listener(&mut self, name: &'static str, id: dioxus_core::ElementId) {
+        // note that we use the foreign event listener here
         self.channel
-            .new_event_listener(name, id.0 as u32, event_bubbles(name) as u8);
+            .foreign_event_listener(name, id.0 as u32, event_bubbles(name) as u8);
     }
 
     fn remove_event_listener(&mut self, name: &'static str, id: dioxus_core::ElementId) {

+ 1 - 0
packages/interpreter/tests/e2e.rs

@@ -0,0 +1 @@
+//! Ensure that the interpreter loads, has no errors, and writes mutations

+ 1 - 0
packages/interpreter/tests/serialize.rs

@@ -0,0 +1 @@
+//! Ensure that events are serialized and deserialized correctly.

+ 20 - 0
packages/interpreter/tsconfig.json

@@ -0,0 +1,20 @@
+{
+    "compilerOptions": {
+        "module": "system",
+        "lib": [
+            "ES2015",
+            "DOM"
+        ],
+        "noImplicitAny": true,
+        "removeComments": true,
+        "preserveConstEnums": true,
+        "outFile": "gen/main.js",
+        "sourceMap": true
+    },
+    "include": [
+        "src/ts/*.ts"
+    ],
+    "exclude": [
+        "**/*.spec.ts"
+    ]
+}

+ 35 - 0
packages/web/NOTES.md

@@ -0,0 +1,35 @@
+
+// ## RequestAnimationFrame and RequestIdleCallback
+// ------------------------------------------------
+// React implements "jank free rendering" by deliberately not blocking the browser's main thread. For large diffs, long
+// running work, and integration with things like React-Three-Fiber, it's extremeley important to avoid blocking the
+// main thread.
+//
+// React solves this problem by breaking up the rendering process into a "diff" phase and a "render" phase. In Dioxus,
+// the diff phase is non-blocking, using "work_with_deadline" to allow the browser to process other events. When the diff phase
+// is  finally complete, the VirtualDOM will return a set of "Mutations" for this crate to apply.
+//
+// Here, we schedule the "diff" phase during the browser's idle period, achieved by calling RequestIdleCallback and then
+// setting a timeout from the that completes when the idleperiod is over. Then, we call requestAnimationFrame
+//
+//     From Google's guide on rAF and rIC:
+//     -----------------------------------
+//
+//     If the callback is fired at the end of the frame, it will be scheduled to go after the current frame has been committed,
+//     which means that style changes will have been applied, and, importantly, layout calculated. If we make DOM changes inside
+//      of the idle callback, those layout calculations will be invalidated. If there are any kind of layout reads in the next
+//      frame, e.g. getBoundingClientRect, clientWidth, etc, the browser will have to perform a Forced Synchronous Layout,
+//      which is a potential performance bottleneck.
+//
+//     Another reason not trigger DOM changes in the idle callback is that the time impact of changing the DOM is unpredictable,
+//     and as such we could easily go past the deadline the browser provided.
+//
+//     The best practice is to only make DOM changes inside of a requestAnimationFrame callback, since it is scheduled by the
+//     browser with that type of work in mind. That means that our code will need to use a document fragment, which can then
+//     be appended in the next requestAnimationFrame callback. If you are using a VDOM library, you would use requestIdleCallback
+//     to make changes, but you would apply the DOM patches in the next requestAnimationFrame callback, not the idle callback.
+//
+//     Essentially:
+//     ------------
+//     - Do the VDOM work during the idlecallback
+//     - Do DOM work in the next requestAnimationFrame callback

+ 1 - 0
packages/web/src/dom.rs

@@ -113,6 +113,7 @@ impl WebsysDom {
         );
         dioxus_html::set_event_converter(Box::new(WebEventConverter));
         handler.forget();
+
         Self {
             document,
             root,

+ 0 - 39
packages/web/src/lib.rs

@@ -20,41 +20,6 @@
 //! To purview the examples, check of the root Dioxus crate - the examples in this crate are mostly meant to provide
 //! validation of websys-specific features and not the general use of Dioxus.
 
-// ## RequestAnimationFrame and RequestIdleCallback
-// ------------------------------------------------
-// React implements "jank free rendering" by deliberately not blocking the browser's main thread. For large diffs, long
-// running work, and integration with things like React-Three-Fiber, it's extremeley important to avoid blocking the
-// main thread.
-//
-// React solves this problem by breaking up the rendering process into a "diff" phase and a "render" phase. In Dioxus,
-// the diff phase is non-blocking, using "work_with_deadline" to allow the browser to process other events. When the diff phase
-// is  finally complete, the VirtualDOM will return a set of "Mutations" for this crate to apply.
-//
-// Here, we schedule the "diff" phase during the browser's idle period, achieved by calling RequestIdleCallback and then
-// setting a timeout from the that completes when the idleperiod is over. Then, we call requestAnimationFrame
-//
-//     From Google's guide on rAF and rIC:
-//     -----------------------------------
-//
-//     If the callback is fired at the end of the frame, it will be scheduled to go after the current frame has been committed,
-//     which means that style changes will have been applied, and, importantly, layout calculated. If we make DOM changes inside
-//      of the idle callback, those layout calculations will be invalidated. If there are any kind of layout reads in the next
-//      frame, e.g. getBoundingClientRect, clientWidth, etc, the browser will have to perform a Forced Synchronous Layout,
-//      which is a potential performance bottleneck.
-//
-//     Another reason not trigger DOM changes in the idle callback is that the time impact of changing the DOM is unpredictable,
-//     and as such we could easily go past the deadline the browser provided.
-//
-//     The best practice is to only make DOM changes inside of a requestAnimationFrame callback, since it is scheduled by the
-//     browser with that type of work in mind. That means that our code will need to use a document fragment, which can then
-//     be appended in the next requestAnimationFrame callback. If you are using a VDOM library, you would use requestIdleCallback
-//     to make changes, but you would apply the DOM patches in the next requestAnimationFrame callback, not the idle callback.
-//
-//     Essentially:
-//     ------------
-//     - Do the VDOM work during the idlecallback
-//     - Do DOM work in the next requestAnimationFrame callback
-
 use std::rc::Rc;
 
 pub use crate::cfg::Config;
@@ -78,10 +43,6 @@ mod hot_reload;
 #[cfg(feature = "hydrate")]
 mod rehydrate;
 
-// Currently disabled since it actually slows down immediate rendering
-// todo: only schedule non-immediate renders through ric/raf
-// mod ric_raf;
-// mod rehydrate;
 
 /// Runs the app as a future that can be scheduled around the main thread.
 ///