From c9612a085ed1a96a5d8acd573337643395efa5ae Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 19 Oct 2023 16:40:08 -0500 Subject: [PATCH] fix liveview mounted event --- packages/desktop/Cargo.toml | 4 +- packages/desktop/build.rs | 2 +- packages/desktop/src/desktop_context.rs | 2 +- packages/interpreter/Cargo.toml | 3 +- .../interpreter/src/sledgehammer_bindings.rs | 290 +++++++++--------- packages/liveview/Cargo.toml | 4 +- packages/liveview/build.rs | 10 +- packages/liveview/src/element.rs | 9 +- packages/liveview/src/main.js | 25 +- packages/liveview/src/pool.rs | 22 +- 10 files changed, 195 insertions(+), 176 deletions(-) diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 57326db98..fcc47e4d8 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react"] [dependencies] dioxus-core = { workspace = true, features = ["serialize"] } dioxus-html = { workspace = true, features = ["serialize", "native-bind"] } -dioxus-interpreter-js = { workspace = true, features = ["sledgehammer"]} +dioxus-interpreter-js = { workspace = true, features = ["binary-protocol"] } dioxus-hot-reload = { workspace = true, optional = true } serde = "1.0.136" @@ -74,7 +74,7 @@ exitcode = "1.1.2" scraper = "0.16.0" [build-dependencies] -dioxus-interpreter-js = { workspace = true, features = ["sledgehammer"] } +dioxus-interpreter-js = { workspace = true, features = ["binary-protocol"] } minify-js = "0.5.6" # These tests need to be run on the main thread, so they cannot use rust's test harness. diff --git a/packages/desktop/build.rs b/packages/desktop/build.rs index 77930cb8c..bd7445637 100644 --- a/packages/desktop/build.rs +++ b/packages/desktop/build.rs @@ -1,4 +1,4 @@ -use dioxus_interpreter_js::SLEDGEHAMMER_JS; +use dioxus_interpreter_js::binary_protocol::SLEDGEHAMMER_JS; use std::io::Write; diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 1b253eafc..de5eefc89 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -8,7 +8,7 @@ use dioxus_core::ScopeState; use dioxus_core::VirtualDom; #[cfg(all(feature = "hot-reload", debug_assertions))] use dioxus_hot_reload::HotReloadMsg; -use dioxus_interpreter_js::Channel; +use dioxus_interpreter_js::binary_protocol::Channel; use rustc_hash::FxHashMap; use slab::Slab; use std::cell::RefCell; diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml index 7dea0fe79..8d155db6d 100644 --- a/packages/interpreter/Cargo.toml +++ b/packages/interpreter/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] wasm-bindgen = { workspace = true, optional = true } js-sys = { version = "0.3.56", optional = true } web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] } -sledgehammer_bindgen = { path = "/Users/evanalmloff/Desktop/Github/sledgehammer_bindgen", default-features = false, optional = true } +sledgehammer_bindgen = { git = "https://github.com/ealmloff/sledgehammer_bindgen", default-features = false, optional = true } sledgehammer_utils = { version = "0.2", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } @@ -23,4 +23,5 @@ default = [] serialize = ["serde"] sledgehammer = ["sledgehammer_bindgen", "sledgehammer_utils"] web = ["sledgehammer", "wasm-bindgen", "js-sys", "web-sys", "sledgehammer_bindgen/web"] +binary-protocol = ["sledgehammer"] minimal_bindings = [] diff --git a/packages/interpreter/src/sledgehammer_bindings.rs b/packages/interpreter/src/sledgehammer_bindings.rs index 6726b48f6..ac1fbf5f0 100644 --- a/packages/interpreter/src/sledgehammer_bindings.rs +++ b/packages/interpreter/src/sledgehammer_bindings.rs @@ -1,10 +1,13 @@ #[cfg(feature = "web")] use js_sys::Function; +#[cfg(feature = "web")] use sledgehammer_bindgen::bindgen; #[cfg(feature = "web")] use web_sys::Node; +#[cfg(feature = "web")] pub const SLEDGEHAMMER_JS: &str = GENERATED_JS; + #[cfg(feature = "web")] #[bindgen(module)] mod js { @@ -285,152 +288,157 @@ mod js { } } -#[cfg(not(feature = "web"))] -#[bindgen] -mod js { - const JS_FILE: &str = "./packages/interpreter/src/interpreter.js"; +#[cfg(feature = "binary-protocol")] +pub mod binary_protocol { + use sledgehammer_bindgen::bindgen; + pub const SLEDGEHAMMER_JS: &str = GENERATED_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) { - "{stack.push(document.createElement($element$))}" - } - fn create_element_ns(element: &'static str, ns: &'static str) { - "{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_name: &str, id: u32, bubbles: u8) { - r#" - bubbles = bubbles == 1; - node = nodes[id]; - if(node.listening){ - node.listening += 1; - } else { - node.listening = 1; + #[bindgen] + mod protocol_js { + const JS_FILE: &str = "./packages/interpreter/src/interpreter.js"; + + fn mount_to_root() { + "{AppendChildren(root, stack.length-1);}" } - node.setAttribute('data-dioxus-id', `\${id}`); - - // if this is a mounted listener, we send the event immediately - if (event_name === "mounted") { - window.ipc.postMessage( - serializeIpcMessage("user_event", { - name: event_name, - element: edit.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, 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, value: &str, ns: &str) { - "{node = nodes[$id$]; SetAttributeInner(node, $field$, $value$, $ns$);}" - } - fn set_top_attribute(field: &str, value: &str, ns: &str) { - "{SetAttributeInner(stack[stack.length-1], $field$, $value$, $ns$);}" - } - fn remove_attribute(id: u32, field: &str, ns: &str) { - 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; + 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]); } - } else if (ns == "style") { - node.style.removeProperty(name); + }" + } + 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) { + "{stack.push(document.createElement($element$))}" + } + fn create_element_ns(element: &'static str, ns: &'static str) { + "{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_name: &str, id: u32, bubbles: u8) { + r#" + bubbles = bubbles == 1; + node = nodes[id]; + if(node.listening){ + node.listening += 1; } else { - node.removeAttributeNS(ns, field); + node.listening = 1; } - }"# - } - 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; + node.setAttribute('data-dioxus-id', `\${id}`); + + // if this is a mounted listener, we send the event immediately + if (event_name === "mounted") { + window.ipc.postMessage( + serializeIpcMessage("user_event", { + name: event_name, + element: id, + data: null, + bubbles, + }) + ); } 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$);}" + listeners.create(event_name, node, bubbles, (event) => { + handler(event, event_name, bubbles, config); + }); + }"# + } + fn remove_event_listener(event_name: &str, 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, value: &str, ns: &str) { + "{node = nodes[$id$]; SetAttributeInner(node, $field$, $value$, $ns$);}" + } + fn set_top_attribute(field: &str, value: &str, ns: &str) { + "{SetAttributeInner(stack[stack.length-1], $field$, $value$, $ns$);}" + } + fn remove_attribute(id: u32, field: &str, ns: &str) { + 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$);}" + } } } diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml index 512edd16a..008ab48cb 100644 --- a/packages/liveview/Cargo.toml +++ b/packages/liveview/Cargo.toml @@ -25,7 +25,7 @@ serde_json = "1.0.91" rustc-hash = { workspace = true } dioxus-html = { workspace = true, features = ["serialize"] } dioxus-core = { workspace = true, features = ["serialize"] } -dioxus-interpreter-js = { workspace = true, features = ["sledgehammer"] } +dioxus-interpreter-js = { workspace = true, features = ["binary-protocol"] } dioxus-hot-reload = { workspace = true, optional = true } # warp @@ -54,7 +54,7 @@ salvo = { version = "0.44.1", features = ["affix", "ws"] } tower = "0.4.13" [build-dependencies] -dioxus-interpreter-js = { workspace = true, features = ["sledgehammer"] } +dioxus-interpreter-js = { workspace = true, features = ["binary-protocol"] } minify-js = "0.5.6" [features] diff --git a/packages/liveview/build.rs b/packages/liveview/build.rs index fc38e29c4..b62a09da0 100644 --- a/packages/liveview/build.rs +++ b/packages/liveview/build.rs @@ -1,4 +1,4 @@ -use dioxus_interpreter_js::SLEDGEHAMMER_JS; +use dioxus_interpreter_js::binary_protocol::SLEDGEHAMMER_JS; use minify_js::*; use std::io::Write; @@ -55,9 +55,9 @@ fn main() { let js = format!("{interpreter}\n{main_js}"); let session = Session::new(); - let mut out = Vec::new(); - minify(&session, TopLevelMode::Module, js.as_bytes(), &mut out).unwrap(); - let minified = String::from_utf8(out).unwrap(); + // let mut out = Vec::new(); + // minify(&session, TopLevelMode::Module, js.as_bytes(), &mut out).unwrap(); + // let minified = String::from_utf8(out).unwrap(); let mut file = std::fs::File::create("src/minified.js").unwrap(); - file.write_all(minified.as_bytes()).unwrap(); + file.write_all(js.as_bytes()).unwrap(); } diff --git a/packages/liveview/src/element.rs b/packages/liveview/src/element.rs index ae0eb4efe..6fb1fb436 100644 --- a/packages/liveview/src/element.rs +++ b/packages/liveview/src/element.rs @@ -29,7 +29,7 @@ impl RenderedElementBacking for LiveviewElement { >, >, > { - let script = format!("return window.interpreter.GetClientRect({});", self.id.0); + let script = format!("return getClientRect({});", self.id.0); let fut = self .query @@ -53,7 +53,7 @@ impl RenderedElementBacking for LiveviewElement { behavior: dioxus_html::ScrollBehavior, ) -> std::pin::Pin>>> { let script = format!( - "return window.interpreter.ScrollTo({}, {});", + "return scrollTo({}, {});", self.id.0, serde_json::to_string(&behavior).expect("Failed to serialize ScrollBehavior") ); @@ -76,10 +76,7 @@ impl RenderedElementBacking for LiveviewElement { &self, focus: bool, ) -> std::pin::Pin>>> { - let script = format!( - "return window.interpreter.SetFocus({}, {});", - self.id.0, focus - ); + let script = format!("return setFocus({}, {});", self.id.0, focus); let fut = self.query.new_query::(&script).resolve(); diff --git a/packages/liveview/src/main.js b/packages/liveview/src/main.js index 4ca557127..54b575263 100644 --- a/packages/liveview/src/main.js +++ b/packages/liveview/src/main.js @@ -28,19 +28,26 @@ class IPC { }; ws.onmessage = (message) => { - if (message.data instanceof ArrayBuffer) { + console.log(message.data); + const u8view = new Uint8Array(message.data); + const binaryFrame = u8view[0] == 1; + const messageData = message.data.slice(1) + // The first byte tells the shim if this is a binary of text frame + if (binaryFrame) { // binary frame - run_from_bytes(message.data); - } else { + run_from_bytes(messageData); + } + else { // text frame + + let decoder = new TextDecoder("utf-8"); + + // Using decode method to get string output + let str = decoder.decode(messageData); // Ignore pongs - if (message.data != "__pong__") { - const event = JSON.parse(message.data); + if (str != "__pong__") { + const event = JSON.parse(str); switch (event.type) { - case "edits": - let edits = event.data; - window.interpreter.handleEdits(edits); - break; case "query": Function("Eval", `"use strict";${event.data};`)(); break; diff --git a/packages/liveview/src/pool.rs b/packages/liveview/src/pool.rs index a74c183df..64adfa3d1 100644 --- a/packages/liveview/src/pool.rs +++ b/packages/liveview/src/pool.rs @@ -6,7 +6,7 @@ use crate::{ }; use dioxus_core::{prelude::*, BorrowedAttributeValue, Mutations}; use dioxus_html::{event_bubbles, EventData, HtmlEvent, MountedData}; -use dioxus_interpreter_js::Channel; +use dioxus_interpreter_js::binary_protocol::Channel; use futures_util::{pin_mut, SinkExt, StreamExt}; use rustc_hash::FxHashMap; use serde::Serialize; @@ -173,7 +173,7 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li match evt.as_ref().map(|o| o.as_deref()) { // respond with a pong every ping to keep the websocket alive Some(Ok(b"__ping__")) => { - // ws.send(b"__pong__".to_vec()).await?; + ws.send(text_frame("__pong__")).await?; } Some(Ok(evt)) => { if let Ok(message) = serde_json::from_str::(&String::from_utf8_lossy(evt)) { @@ -212,7 +212,7 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li // handle any new queries Some(query) = query_rx.recv() => { - // ws.send(serde_json::to_string(&ClientUpdate::Query(query)).unwrap().into_bytes()).await?; + ws.send(text_frame(&serde_json::to_string(&ClientUpdate::Query(query)).unwrap())).await?; } Some(msg) = hot_reload_wait => { @@ -247,13 +247,19 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li } } +fn text_frame(text: &str) -> Vec { + let mut bytes = vec![0]; + bytes.extend(text.as_bytes()); + bytes +} + fn add_template( template: &Template<'static>, channel: &mut Channel, templates: &mut FxHashMap, max_template_count: &mut u16, ) { - for (idx, root) in template.roots.iter().enumerate() { + for root in template.roots.iter() { create_template_node(channel, root); templates.insert(template.name.to_owned(), *max_template_count); } @@ -368,16 +374,16 @@ fn apply_edits( } } - let bytes: Vec<_> = channel.export_memory().collect(); + // Add an extra one at the beginning to tell the shim this is a binary frame + let mut bytes = vec![1]; + bytes.extend(channel.export_memory()); channel.reset(); Some(bytes) } #[derive(Serialize)] #[serde(tag = "type", content = "data")] -enum ClientUpdate<'a> { - #[serde(rename = "edits")] - Edits(Mutations<'a>), +enum ClientUpdate { #[serde(rename = "query")] Query(String), }