diff --git a/packages/desktop/src/index.html b/packages/desktop/src/index.html index 888ab8437..c17a63d2a 100644 --- a/packages/desktop/src/index.html +++ b/packages/desktop/src/index.html @@ -15,5 +15,6 @@ + diff --git a/packages/jsinterpreter/README.md b/packages/jsinterpreter/README.md new file mode 100644 index 000000000..bab57ac16 --- /dev/null +++ b/packages/jsinterpreter/README.md @@ -0,0 +1,8 @@ +# JS Interpreter + +After diffing old and new trees, the Dioxus VirtualDom produces patches that are used to modify the existing Dom. We can send these patches anywhere - including targets without WASM support. + +In renderers with support for JavaScript, we use the interpreter from this repository - written in TypeScript - to patch the Dom. This lets us circumvent any overhead on the Rust <-> Dom boundary and keep consistency in our interpreter implementation in web/webview targets. + + +For now - both Dioxus Web and Dioxus Desktop (webview) use the same interpreter code with tweaks. diff --git a/packages/jsinterpreter/interpreter.js b/packages/jsinterpreter/interpreter.js index 6139280f8..453b34406 100644 --- a/packages/jsinterpreter/interpreter.js +++ b/packages/jsinterpreter/interpreter.js @@ -1,23 +1,22 @@ -"use strict"; -exports.__esModule = true; -exports.Interpreter = void 0; function serialize_event(event) { var _a, _b; switch (event.type) { case "copy": case "cut": - case "past": + case "past": { return {}; + } case "compositionend": case "compositionstart": - case "compositionupdate": + case "compositionupdate": { var data = event.data; return { - data: data + data: data, }; + } case "keydown": case "keypress": - case "keyup": + case "keyup": { var _c = event, charCode = _c.charCode, key = _c.key, altKey = _c.altKey, ctrlKey = _c.ctrlKey, metaKey = _c.metaKey, keyCode = _c.keyCode, shiftKey = _c.shiftKey, location_1 = _c.location, repeat = _c.repeat, which = _c.which; return { char_code: charCode, @@ -30,12 +29,14 @@ function serialize_event(event) { location: location_1, repeat: repeat, which: which, - locale: "locale" + locale: "locale", }; + } case "focus": - case "blur": + case "blur": { return {}; - case "change": + } + case "change": { var target = event.target; var value = void 0; if (target.type === "checkbox" || target.type === "radio") { @@ -45,19 +46,20 @@ function serialize_event(event) { value = (_a = target.value) !== null && _a !== void 0 ? _a : target.textContent; } return { - value: value + value: value, }; + } case "input": case "invalid": case "reset": case "submit": { - var target_1 = event.target; - var value_1 = (_b = target_1.value) !== null && _b !== void 0 ? _b : target_1.textContent; - if (target_1.type == "checkbox") { - value_1 = target_1.checked ? "true" : "false"; + var target = event.target; + var value = (_b = target.value) !== null && _b !== void 0 ? _b : target.textContent; + if (target.type == "checkbox") { + value = target.checked ? "true" : "false"; } return { - value: value_1 + value: value, }; } case "click": @@ -78,20 +80,20 @@ function serialize_event(event) { case "mouseout": case "mouseover": case "mouseup": { - var _d = event, altKey_1 = _d.altKey, button = _d.button, buttons = _d.buttons, clientX = _d.clientX, clientY = _d.clientY, ctrlKey_1 = _d.ctrlKey, metaKey_1 = _d.metaKey, pageX = _d.pageX, pageY = _d.pageY, screenX_1 = _d.screenX, screenY_1 = _d.screenY, shiftKey_1 = _d.shiftKey; + var _d = event, altKey = _d.altKey, button = _d.button, buttons = _d.buttons, clientX = _d.clientX, clientY = _d.clientY, ctrlKey = _d.ctrlKey, metaKey = _d.metaKey, pageX = _d.pageX, pageY = _d.pageY, screenX_1 = _d.screenX, screenY_1 = _d.screenY, shiftKey = _d.shiftKey; return { - alt_key: altKey_1, + alt_key: altKey, button: button, buttons: buttons, client_x: clientX, client_y: clientY, - ctrl_key: ctrlKey_1, - meta_key: metaKey_1, + ctrl_key: ctrlKey, + meta_key: metaKey, page_x: pageX, page_y: pageY, screen_x: screenX_1, screen_y: screenY_1, - shift_key: shiftKey_1 + shift_key: shiftKey, }; } case "pointerdown": @@ -104,20 +106,20 @@ function serialize_event(event) { case "pointerleave": case "pointerover": case "pointerout": { - var _e = event, altKey_2 = _e.altKey, button = _e.button, buttons = _e.buttons, clientX = _e.clientX, clientY = _e.clientY, ctrlKey_2 = _e.ctrlKey, metaKey_2 = _e.metaKey, pageX = _e.pageX, pageY = _e.pageY, screenX_2 = _e.screenX, screenY_2 = _e.screenY, shiftKey_2 = _e.shiftKey, pointerId = _e.pointerId, width = _e.width, height = _e.height, pressure = _e.pressure, tangentialPressure = _e.tangentialPressure, tiltX = _e.tiltX, tiltY = _e.tiltY, twist = _e.twist, pointerType = _e.pointerType, isPrimary = _e.isPrimary; + var _e = event, altKey = _e.altKey, button = _e.button, buttons = _e.buttons, clientX = _e.clientX, clientY = _e.clientY, ctrlKey = _e.ctrlKey, metaKey = _e.metaKey, pageX = _e.pageX, pageY = _e.pageY, screenX_2 = _e.screenX, screenY_2 = _e.screenY, shiftKey = _e.shiftKey, pointerId = _e.pointerId, width = _e.width, height = _e.height, pressure = _e.pressure, tangentialPressure = _e.tangentialPressure, tiltX = _e.tiltX, tiltY = _e.tiltY, twist = _e.twist, pointerType = _e.pointerType, isPrimary = _e.isPrimary; return { - alt_key: altKey_2, + alt_key: altKey, button: button, buttons: buttons, client_x: clientX, client_y: clientY, - ctrl_key: ctrlKey_2, - meta_key: metaKey_2, + ctrl_key: ctrlKey, + meta_key: metaKey, page_x: pageX, page_y: pageY, screen_x: screenX_2, screen_y: screenY_2, - shift_key: shiftKey_2, + shift_key: shiftKey, pointer_id: pointerId, width: width, height: height, @@ -127,35 +129,37 @@ function serialize_event(event) { tilt_y: tiltY, twist: twist, pointer_type: pointerType, - is_primary: isPrimary + is_primary: isPrimary, }; } - case "select": + case "select": { return {}; + } case "touchcancel": case "touchend": case "touchmove": case "touchstart": { - var _f = event, altKey_3 = _f.altKey, ctrlKey_3 = _f.ctrlKey, metaKey_3 = _f.metaKey, shiftKey_3 = _f.shiftKey; + var _f = event, altKey = _f.altKey, ctrlKey = _f.ctrlKey, metaKey = _f.metaKey, shiftKey = _f.shiftKey; return { // changed_touches: event.changedTouches, // target_touches: event.targetTouches, // touches: event.touches, - alt_key: altKey_3, - ctrl_key: ctrlKey_3, - meta_key: metaKey_3, - shift_key: shiftKey_3 + alt_key: altKey, + ctrl_key: ctrlKey, + meta_key: metaKey, + shift_key: shiftKey, }; } - case "scroll": + case "scroll": { return {}; + } case "wheel": { var _g = event, deltaX = _g.deltaX, deltaY = _g.deltaY, deltaZ = _g.deltaZ, deltaMode = _g.deltaMode; return { delta_x: deltaX, delta_y: deltaY, delta_z: deltaZ, - delta_mode: deltaMode + delta_mode: deltaMode, }; } case "animationstart": @@ -165,7 +169,7 @@ function serialize_event(event) { return { animation_name: animationName, elapsed_time: elapsedTime, - pseudo_element: pseudoElement + pseudo_element: pseudoElement, }; } case "transitionend": { @@ -173,7 +177,7 @@ function serialize_event(event) { return { property_name: propertyName, elapsed_time: elapsedTime, - pseudo_element: pseudoElement + pseudo_element: pseudoElement, }; } case "abort": @@ -198,12 +202,15 @@ function serialize_event(event) { case "suspend": case "timeupdate": case "volumechange": - case "waiting": + case "waiting": { return {}; - case "toggle": + } + case "toggle": { return {}; - default: + } + default: { return {}; + } } } var bool_attrs = { @@ -214,7 +221,7 @@ var bool_attrs = { autoplay: true, checked: true, controls: true, - "default": true, + default: true, defer: true, disabled: true, formnovalidate: true, @@ -232,13 +239,14 @@ var bool_attrs = { required: true, reversed: true, selected: true, - truespeed: true + truespeed: true, }; var Interpreter = /** @class */ (function () { function Interpreter(root) { this.root = root; this.stack = [root]; this.listeners = {}; + this.handlers = {}; this.lastNodeWasText = false; this.nodes = [root]; } @@ -303,17 +311,28 @@ var Interpreter = /** @class */ (function () { this.stack.push(el); this.nodes[root] = el; }; - Interpreter.prototype.NewEventListener = function (event_name, scope, root) { - console.log('new event listener', event_name, root, scope); + Interpreter.prototype.NewEventListener = function (event_name, scope, root, handler) { + // console.log('new event listener', event_name, root, scope); var element = this.nodes[root]; element.setAttribute("dioxus-event-".concat(event_name), "".concat(scope, ".").concat(root)); - // if (!this.listeners[event_name]) { - // this.listeners[event_name] = handler; - // this.root.addEventListener(event_name, handler); - // } + if (this.listeners[event_name] === undefined) { + this.listeners[event_name] = 0; + this.handlers[event_name] = handler; + this.root.addEventListener(event_name, handler); + } + else { + this.listeners[event_name]++; + } }; - Interpreter.prototype.RemoveEventListener = function (root, event_name, scope) { - // + Interpreter.prototype.RemoveEventListener = function (root, event_name) { + var element = this.nodes[root]; + element.removeAttribute("dioxus-event-".concat(event_name)); + this.listeners[event_name]--; + if (this.listeners[event_name] === 0) { + this.root.removeEventListener(event_name, this.handlers[event_name]); + delete this.listeners[event_name]; + delete this.handlers[event_name]; + } }; Interpreter.prototype.SetText = function (root, text) { this.nodes[root].textContent = text; @@ -369,7 +388,6 @@ var Interpreter = /** @class */ (function () { } }; Interpreter.prototype.handleEdits = function (edits) { - console.log("handling edits ", edits); this.stack.push(this.root); for (var _i = 0, edits_1 = edits; _i < edits_1.length; _i++) { var edit = edits_1[_i]; @@ -409,13 +427,13 @@ var Interpreter = /** @class */ (function () { this.CreatePlaceholder(edit.root); break; case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name, edit.scope); + this.RemoveEventListener(edit.root, edit.event_name); break; case "NewEventListener": // todo: only on desktop should we make our own handler var handler = function (event) { var target = event.target; - console.log("event", event); + // console.log("event", event); if (target != null) { var real_id = target.getAttribute("dioxus-id"); var should_prevent_default = target.getAttribute("dioxus-prevent-default"); @@ -429,12 +447,11 @@ var Interpreter = /** @class */ (function () { window.rpc.call("user_event", { event: edit.event_name, mounted_dom_id: parseInt(real_id), - contents: contents + contents: contents, }); } }; - this.NewEventListener(edit.event_name, edit.scope, edit.root); - // this.NewEventListener(edit, handler); + this.NewEventListener(edit.event_name, edit.scope, edit.root, handler); break; case "SetText": this.SetText(edit.root, edit.text); @@ -449,7 +466,7 @@ var Interpreter = /** @class */ (function () { }; return Interpreter; }()); -exports.Interpreter = Interpreter; +export { Interpreter }; function main() { var root = window.document.getElementById("main"); if (root != null) { @@ -457,4 +474,4 @@ function main() { window.rpc.call("initialize"); } } -main(); +//# sourceMappingURL=interpreter.js.map \ No newline at end of file diff --git a/packages/jsinterpreter/interpreter.ts b/packages/jsinterpreter/interpreter.ts index e3547effb..17617a209 100644 --- a/packages/jsinterpreter/interpreter.ts +++ b/packages/jsinterpreter/interpreter.ts @@ -1,23 +1,23 @@ - - function serialize_event(event: Event) { switch (event.type) { case "copy": case "cut": - case "past": + case "past": { return {}; + } case "compositionend": case "compositionstart": - case "compositionupdate": + case "compositionupdate": { let { data } = (event as CompositionEvent); return { data, }; + } case "keydown": case "keypress": - case "keyup": + case "keyup": { let { charCode, key, @@ -44,12 +44,14 @@ function serialize_event(event: Event) { which: which, locale: "locale", }; + } case "focus": - case "blur": + case "blur": { return {}; + } - case "change": + case "change": { let target = event.target as HTMLInputElement; let value; if (target.type === "checkbox" || target.type === "radio") { @@ -61,6 +63,7 @@ function serialize_event(event: Event) { return { value: value, }; + } case "input": case "invalid": @@ -187,8 +190,9 @@ function serialize_event(event: Event) { }; } - case "select": + case "select": { return {}; + } case "touchcancel": case "touchend": @@ -211,8 +215,10 @@ function serialize_event(event: Event) { }; } - case "scroll": + case "scroll": { return {}; + } + case "wheel": { const { deltaX, @@ -278,14 +284,17 @@ function serialize_event(event: Event) { case "suspend": case "timeupdate": case "volumechange": - case "waiting": + case "waiting": { return {}; + } - case "toggle": + case "toggle": { return {}; + } - default: + default: { return {}; + } } } @@ -321,7 +330,8 @@ const bool_attrs = { export class Interpreter { root: Element; stack: Element[]; - listeners: { [key: string]: (event: Event) => void }; + listeners: { [key: string]: number }; + handlers: { [key: string]: (evt: Event) => void }; lastNodeWasText: boolean; nodes: Element[]; @@ -329,8 +339,8 @@ export class Interpreter { constructor(root: Element) { this.root = root; this.stack = [root]; - this.listeners = { - }; + this.listeners = {}; + this.handlers = {}; this.lastNodeWasText = false; this.nodes = [root]; } @@ -412,23 +422,36 @@ export class Interpreter { this.nodes[root] = el; } - NewEventListener(event_name: string, scope: number, root: number) { - console.log('new event listener', event_name, root, scope); + NewEventListener(event_name: string, scope: number, root: number, handler: (evt: Event) => void) { + // console.log('new event listener', event_name, root, scope); const element = this.nodes[root]; element.setAttribute( `dioxus-event-${event_name}`, `${scope}.${root}` ); - // if (!this.listeners[event_name]) { - // this.listeners[event_name] = handler; - // this.root.addEventListener(event_name, handler); - // } + if (this.listeners[event_name] === undefined) { + this.listeners[event_name] = 0; + this.handlers[event_name] = handler; + this.root.addEventListener(event_name, handler); + } else { + this.listeners[event_name]++; + } } + RemoveEventListener(root: number, event_name: string) { + const element = this.nodes[root]; + element.removeAttribute( + `dioxus-event-${event_name}` + ); - RemoveEventListener(root: number, event_name: string, scope: number) { - // + this.listeners[event_name]--; + + if (this.listeners[event_name] === 0) { + this.root.removeEventListener(event_name, this.handlers[event_name]); + delete this.listeners[event_name]; + delete this.handlers[event_name]; + } } @@ -492,7 +515,6 @@ export class Interpreter { } handleEdits(edits: DomEdit[]) { - console.log("handling edits ", edits); this.stack.push(this.root); for (let edit of edits) { @@ -533,13 +555,13 @@ export class Interpreter { this.CreatePlaceholder(edit.root); break; case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name, edit.scope); + this.RemoveEventListener(edit.root, edit.event_name); break; case "NewEventListener": // todo: only on desktop should we make our own handler let handler = (event: Event) => { const target = event.target as Element | null; - console.log("event", event); + // console.log("event", event); if (target != null) { const real_id = target.getAttribute(`dioxus-id`); @@ -565,8 +587,7 @@ export class Interpreter { }); } }; - this.NewEventListener(edit.event_name, edit.scope, edit.root); - // this.NewEventListener(edit, handler); + this.NewEventListener(edit.event_name, edit.scope, edit.root, handler); break; case "SetText": this.SetText(edit.root, edit.text); diff --git a/packages/jsinterpreter/tsconfig.json b/packages/jsinterpreter/tsconfig.json index 8f39daddf..883760881 100644 --- a/packages/jsinterpreter/tsconfig.json +++ b/packages/jsinterpreter/tsconfig.json @@ -1,9 +1,13 @@ { "compilerOptions": { - "target": "es5", - "module": "commonjs", - "strict": true, - "outDir": "dist", - "sourceMap": true + "target": "ES5", + "module": "ESNext", + "lib": [ + "es2015", + "es5", + "es6", + "dom" + ], + "strict": true } } diff --git a/packages/web/src/bindings.rs b/packages/web/src/bindings.rs index d65fc1300..cc4202886 100644 --- a/packages/web/src/bindings.rs +++ b/packages/web/src/bindings.rs @@ -1,8 +1,8 @@ -use dioxus_core::DomEdit; -use wasm_bindgen::{convert::IntoWasmAbi, describe::WasmDescribe, prelude::*}; -use web_sys::{Element, HtmlElement, Node}; +use js_sys::Function; +use wasm_bindgen::prelude::*; +use web_sys::{Element, Node}; -#[wasm_bindgen(module = "/src/interpreter.js")] +#[wasm_bindgen(module = "/../jsinterpreter/interpreter.js")] extern "C" { pub type Interpreter; @@ -43,7 +43,13 @@ extern "C" { pub fn CreatePlaceholder(this: &Interpreter, root: u64); #[wasm_bindgen(method)] - pub fn NewEventListener(this: &Interpreter, name: &str, scope: usize, root: u64); + pub fn NewEventListener( + this: &Interpreter, + name: &str, + scope: usize, + root: u64, + handler: &Function, + ); #[wasm_bindgen(method)] pub fn RemoveEventListener(this: &Interpreter, root: u64, name: &str); diff --git a/packages/web/src/cache.rs b/packages/web/src/cache.rs index 190b505c1..e17203a65 100644 --- a/packages/web/src/cache.rs +++ b/packages/web/src/cache.rs @@ -1,4 +1,4 @@ -pub static BUILTIN_INTERNED_STRINGS: &[&'static str] = &[ +pub static BUILTIN_INTERNED_STRINGS: &[&str] = &[ // Important tags to dioxus "dioxus-id", "dioxus", diff --git a/packages/web/src/cfg.rs b/packages/web/src/cfg.rs index 0fa0dc48c..cc5d18bf7 100644 --- a/packages/web/src/cfg.rs +++ b/packages/web/src/cfg.rs @@ -3,10 +3,9 @@ /// This struct helps configure the specifics of hydration and render destination for WebSys. /// /// # Example +/// /// ```rust, ignore -/// fn main() { -/// dioxus::web::launch(App, |cfg| cfg.hydrate(true).root_name("myroot")) -/// } +/// dioxus::web::launch(App, |cfg| cfg.hydrate(true).root_name("myroot")) /// ``` pub struct WebConfig { pub(crate) hydrate: bool, diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 918af4c4a..c4bd6a6e5 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -8,51 +8,50 @@ //! - Partial delegation?> use crate::bindings::Interpreter; -use dioxus_core::{DomEdit, ElementId, SchedulerMsg, ScopeId, UserEvent}; -use fxhash::FxHashMap; -use std::{any::Any, fmt::Debug, rc::Rc, sync::Arc}; -use wasm_bindgen::{closure::Closure, JsCast, JsValue}; -use web_sys::{ - CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement, - HtmlOptionElement, HtmlTextAreaElement, Node, -}; +use dioxus_core::{DomEdit, ElementId, SchedulerMsg, UserEvent}; +use js_sys::Function; +use std::{any::Any, rc::Rc, sync::Arc}; +use wasm_bindgen::{closure::Closure, JsCast}; +use web_sys::{Document, Element, Event, HtmlElement}; -use crate::{nodeslab::NodeSlab, WebConfig}; +use crate::WebConfig; pub struct WebsysDom { - document: Document, - pub interpreter: Interpreter, pub(crate) root: Element, - sender_callback: Rc, - - // map of listener types to number of those listeners - // This is roughly a delegater - // TODO: check how infero delegates its events - some are more performant - listeners: FxHashMap<&'static str, ListenerEntry>, + handler: Closure, } -type ListenerEntry = (usize, Closure); - impl WebsysDom { pub fn new(cfg: WebConfig, sender_callback: Rc) -> Self { - let document = load_document(); + // eventually, we just want to let the interpreter do all the work of decoding events into our event type + let callback: Box = Box::new(move |event: &web_sys::Event| { + if let Ok(synthetic_event) = decode_trigger(event) { + // Try to prevent default if the attribute is set + if let Some(target) = event.target() { + if let Some(node) = target.dyn_ref::() { + if let Some(name) = node.get_attribute("dioxus-prevent-default") { + if name == synthetic_event.name + || name.trim_start_matches("on") == synthetic_event.name + { + log::trace!("Preventing default"); + event.prevent_default(); + } + } + } + } - let listeners = FxHashMap::default(); - - let mut stack = Stack::with_capacity(10); + sender_callback.as_ref()(SchedulerMsg::Event(synthetic_event)) + } + }); let root = load_document().get_element_by_id(&cfg.rootname).unwrap(); - let root_node = root.clone().dyn_into::().unwrap(); - stack.push(root_node); Self { interpreter: Interpreter::new(root.clone()), - listeners, - document, - sender_callback, + handler: Closure::wrap(callback), root, } } @@ -78,7 +77,12 @@ impl WebsysDom { event_name, scope, root, - } => self.interpreter.NewEventListener(event_name, scope.0, root), + } => { + // + let handler: &Function = self.handler.as_ref().unchecked_ref(); + self.interpreter + .NewEventListener(event_name, scope.0, root, handler); + } DomEdit::RemoveEventListener { root, event } => { self.interpreter.RemoveEventListener(root, event) } @@ -97,37 +101,6 @@ impl WebsysDom { } } -#[derive(Debug, Default)] -struct Stack { - list: Vec, -} - -impl Stack { - #[inline] - fn with_capacity(cap: usize) -> Self { - Stack { - list: Vec::with_capacity(cap), - } - } - - #[inline] - fn push(&mut self, node: Node) { - self.list.push(node); - } - - #[inline] - fn pop(&mut self) -> Node { - self.list.pop().unwrap() - } - - fn top(&self) -> &Node { - match self.list.last() { - Some(a) => a, - None => panic!("Called 'top' of an empty stack, make sure to push the root first"), - } - } -} - pub struct DioxusWebsysEvent(web_sys::Event); // safety: currently the web is not multithreaded and our VirtualDom exists on the same thread @@ -267,6 +240,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc Arc::new(()), "wheel" => { let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap(); diff --git a/packages/web/src/interpreter.js b/packages/web/src/interpreter.js deleted file mode 100644 index 1d0f14c68..000000000 --- a/packages/web/src/interpreter.js +++ /dev/null @@ -1,529 +0,0 @@ -"use strict"; -var exports = {}; -exports.__esModule = true; -exports.Interpreter = void 0; -function serialize_event(event) { - var _a, _b; - switch (event.type) { - case "copy": - case "cut": - case "past": - return {}; - case "compositionend": - case "compositionstart": - case "compositionupdate": - var data = event.data; - return { - data: data, - }; - case "keydown": - case "keypress": - case "keyup": - var _c = event, - charCode = _c.charCode, - key = _c.key, - altKey = _c.altKey, - ctrlKey = _c.ctrlKey, - metaKey = _c.metaKey, - keyCode = _c.keyCode, - shiftKey = _c.shiftKey, - location_1 = _c.location, - repeat = _c.repeat, - which = _c.which; - return { - char_code: charCode, - key: key, - alt_key: altKey, - ctrl_key: ctrlKey, - meta_key: metaKey, - key_code: keyCode, - shift_key: shiftKey, - location: location_1, - repeat: repeat, - which: which, - locale: "locale", - }; - case "focus": - case "blur": - return {}; - case "change": - var target = event.target; - var value = void 0; - if (target.type === "checkbox" || target.type === "radio") { - value = target.checked ? "true" : "false"; - } else { - value = - (_a = target.value) !== null && _a !== void 0 - ? _a - : target.textContent; - } - return { - value: value, - }; - case "input": - case "invalid": - case "reset": - case "submit": { - var target_1 = event.target; - var value_1 = - (_b = target_1.value) !== null && _b !== void 0 - ? _b - : target_1.textContent; - if (target_1.type == "checkbox") { - value_1 = target_1.checked ? "true" : "false"; - } - return { - value: value_1, - }; - } - case "click": - case "contextmenu": - case "doubleclick": - case "drag": - case "dragend": - case "dragenter": - case "dragexit": - case "dragleave": - case "dragover": - case "dragstart": - case "drop": - case "mousedown": - case "mouseenter": - case "mouseleave": - case "mousemove": - case "mouseout": - case "mouseover": - case "mouseup": { - var _d = event, - altKey_1 = _d.altKey, - button = _d.button, - buttons = _d.buttons, - clientX = _d.clientX, - clientY = _d.clientY, - ctrlKey_1 = _d.ctrlKey, - metaKey_1 = _d.metaKey, - pageX = _d.pageX, - pageY = _d.pageY, - screenX_1 = _d.screenX, - screenY_1 = _d.screenY, - shiftKey_1 = _d.shiftKey; - return { - alt_key: altKey_1, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey_1, - meta_key: metaKey_1, - page_x: pageX, - page_y: pageY, - screen_x: screenX_1, - screen_y: screenY_1, - shift_key: shiftKey_1, - }; - } - case "pointerdown": - case "pointermove": - case "pointerup": - case "pointercancel": - case "gotpointercapture": - case "lostpointercapture": - case "pointerenter": - case "pointerleave": - case "pointerover": - case "pointerout": { - var _e = event, - altKey_2 = _e.altKey, - button = _e.button, - buttons = _e.buttons, - clientX = _e.clientX, - clientY = _e.clientY, - ctrlKey_2 = _e.ctrlKey, - metaKey_2 = _e.metaKey, - pageX = _e.pageX, - pageY = _e.pageY, - screenX_2 = _e.screenX, - screenY_2 = _e.screenY, - shiftKey_2 = _e.shiftKey, - pointerId = _e.pointerId, - width = _e.width, - height = _e.height, - pressure = _e.pressure, - tangentialPressure = _e.tangentialPressure, - tiltX = _e.tiltX, - tiltY = _e.tiltY, - twist = _e.twist, - pointerType = _e.pointerType, - isPrimary = _e.isPrimary; - return { - alt_key: altKey_2, - button: button, - buttons: buttons, - client_x: clientX, - client_y: clientY, - ctrl_key: ctrlKey_2, - meta_key: metaKey_2, - page_x: pageX, - page_y: pageY, - screen_x: screenX_2, - screen_y: screenY_2, - shift_key: shiftKey_2, - 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": { - var _f = event, - altKey_3 = _f.altKey, - ctrlKey_3 = _f.ctrlKey, - metaKey_3 = _f.metaKey, - shiftKey_3 = _f.shiftKey; - return { - // changed_touches: event.changedTouches, - // target_touches: event.targetTouches, - // touches: event.touches, - alt_key: altKey_3, - ctrl_key: ctrlKey_3, - meta_key: metaKey_3, - shift_key: shiftKey_3, - }; - } - case "scroll": - return {}; - case "wheel": { - var _g = event, - deltaX = _g.deltaX, - deltaY = _g.deltaY, - deltaZ = _g.deltaZ, - deltaMode = _g.deltaMode; - return { - delta_x: deltaX, - delta_y: deltaY, - delta_z: deltaZ, - delta_mode: deltaMode, - }; - } - case "animationstart": - case "animationend": - case "animationiteration": { - var _h = event, - animationName = _h.animationName, - elapsedTime = _h.elapsedTime, - pseudoElement = _h.pseudoElement; - return { - animation_name: animationName, - elapsed_time: elapsedTime, - pseudo_element: pseudoElement, - }; - } - case "transitionend": { - var _j = event, - propertyName = _j.propertyName, - elapsedTime = _j.elapsedTime, - pseudoElement = _j.pseudoElement; - 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 {}; - } -} -var 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, -}; -export var Interpreter = /** @class */ (function () { - function Interpreter(root) { - this.root = root; - this.stack = [root]; - this.listeners = {}; - this.lastNodeWasText = false; - this.nodes = [root]; - } - Interpreter.prototype.top = function () { - return this.stack[this.stack.length - 1]; - }; - Interpreter.prototype.pop = function () { - return this.stack.pop(); - }; - Interpreter.prototype.PushRoot = function (root) { - var node = this.nodes[root]; - this.stack.push(node); - }; - Interpreter.prototype.AppendChildren = function (many) { - var root = this.stack[this.stack.length - (1 + many)]; - var to_add = this.stack.splice(this.stack.length - many); - for (var i = 0; i < many; i++) { - root.appendChild(to_add[i]); - } - }; - Interpreter.prototype.ReplaceWith = function (root_id, m) { - var root = this.nodes[root_id]; - var els = this.stack.splice(this.stack.length - m); - root.replaceWith.apply(root, els); - }; - Interpreter.prototype.InsertAfter = function (root, n) { - var old = this.nodes[root]; - var new_nodes = this.stack.splice(this.stack.length - n); - old.after.apply(old, new_nodes); - }; - Interpreter.prototype.InsertBefore = function (root, n) { - var old = this.nodes[root]; - var new_nodes = this.stack.splice(this.stack.length - n); - old.before.apply(old, new_nodes); - }; - Interpreter.prototype.Remove = function (root) { - var node = this.nodes[root]; - if (node !== undefined) { - node.remove(); - } - }; - Interpreter.prototype.CreateTextNode = function (text, root) { - // todo: make it so the types are okay - var node = document.createTextNode(text); - this.nodes[root] = node; - this.stack.push(node); - }; - Interpreter.prototype.CreateElement = function (tag, root) { - var el = document.createElement(tag); - el.setAttribute("dioxus-id", "".concat(root)); - this.nodes[root] = el; - this.stack.push(el); - }; - Interpreter.prototype.CreateElementNs = function (tag, root, ns) { - var el = document.createElementNS(ns, tag); - this.stack.push(el); - this.nodes[root] = el; - }; - Interpreter.prototype.CreatePlaceholder = function (root) { - var el = document.createElement("pre"); - el.hidden = true; - this.stack.push(el); - this.nodes[root] = el; - }; - Interpreter.prototype.NewEventListener = function (event_name, scope, root) { - console.log("new event listener", event_name, root, scope); - var element = this.nodes[root]; - element.setAttribute( - "dioxus-event-".concat(event_name), - "".concat(scope, ".").concat(root) - ); - // if (!this.listeners[event_name]) { - // this.listeners[event_name] = handler; - // this.root.addEventListener(event_name, handler); - // } - }; - Interpreter.prototype.RemoveEventListener = function ( - root, - event_name, - scope - ) { - // - }; - Interpreter.prototype.SetText = function (root, text) { - this.nodes[root].textContent = text; - }; - Interpreter.prototype.SetAttribute = function (root, field, value, ns) { - var name = field; - var node = this.nodes[root]; - if (ns == "style") { - // @ts-ignore - node.style[name] = value; - } else if (ns != null || ns != undefined) { - node.setAttributeNS(ns, name, value); - } else { - switch (name) { - case "value": - if (value != node.value) { - node.value = value; - } - break; - case "checked": - node.checked = value === "true"; - break; - case "selected": - node.selected = value === "true"; - 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 (value == "false" && bool_attrs.hasOwnProperty(name)) { - node.removeAttribute(name); - } else { - node.setAttribute(name, value); - } - } - } - }; - Interpreter.prototype.RemoveAttribute = function (root, name) { - var node = this.nodes[root]; - node.removeAttribute(name); - if (name === "value") { - node.value = ""; - } - if (name === "checked") { - node.checked = false; - } - if (name === "selected") { - node.selected = false; - } - }; - Interpreter.prototype.handleEdits = function (edits) { - console.log("handling edits ", edits); - this.stack.push(this.root); - for (var _i = 0, edits_1 = edits; _i < edits_1.length; _i++) { - var edit = edits_1[_i]; - this.handleEdit(edit); - } - }; - Interpreter.prototype.handleEdit = function (edit) { - switch (edit.type) { - case "PushRoot": - this.PushRoot(edit.root); - break; - case "AppendChildren": - this.AppendChildren(edit.many); - break; - case "ReplaceWith": - this.ReplaceWith(edit.root, edit.m); - break; - case "InsertAfter": - this.InsertAfter(edit.root, edit.n); - break; - case "InsertBefore": - this.InsertBefore(edit.root, edit.n); - break; - case "Remove": - this.Remove(edit.root); - break; - case "CreateTextNode": - this.CreateTextNode(edit.text, edit.root); - break; - case "CreateElement": - this.CreateElement(edit.tag, edit.root); - break; - case "CreateElementNs": - this.CreateElementNs(edit.tag, edit.root, edit.ns); - break; - case "CreatePlaceholder": - this.CreatePlaceholder(edit.root); - break; - case "RemoveEventListener": - this.RemoveEventListener(edit.root, edit.event_name, edit.scope); - break; - case "NewEventListener": - // todo: only on desktop should we make our own handler - var handler = function (event) { - var target = event.target; - console.log("event", event); - if (target != null) { - var real_id = target.getAttribute("dioxus-id"); - var should_prevent_default = target.getAttribute( - "dioxus-prevent-default" - ); - var contents = serialize_event(event); - if (should_prevent_default === "on".concat(event.type)) { - event.preventDefault(); - } - if (real_id == null) { - return; - } - window.rpc.call("user_event", { - event: edit.event_name, - mounted_dom_id: parseInt(real_id), - contents: contents, - }); - } - }; - this.NewEventListener(edit.event_name, edit.scope, edit.root); - // this.NewEventListener(edit, handler); - break; - case "SetText": - this.SetText(edit.root, edit.text); - break; - case "SetAttribute": - this.SetAttribute(edit.root, edit.field, edit.value, edit.ns); - break; - case "RemoveAttribute": - this.RemoveAttribute(edit.root, edit.name); - break; - } - }; - return Interpreter; -})(); -exports.Interpreter = Interpreter; -function main() { - var root = window.document.getElementById("main"); - if (root != null) { - window.interpreter = new Interpreter(root); - window.rpc.call("initialize"); - } -} diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index ba9398276..8c0c0c3b4 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -65,7 +65,6 @@ pub(crate) mod bindings; mod cache; mod cfg; mod dom; -mod nodeslab; mod rehydrate; mod ric_raf; diff --git a/packages/web/src/nodeslab.rs b/packages/web/src/nodeslab.rs deleted file mode 100644 index b986bd34e..000000000 --- a/packages/web/src/nodeslab.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! This module provides a mirror of the VirtualDOM Element Slab using a Vector. - -use std::ops::{Index, IndexMut}; -use web_sys::Node; - -pub(crate) struct NodeSlab { - nodes: Vec>, -} - -impl NodeSlab { - pub fn new(capacity: usize) -> NodeSlab { - let nodes = Vec::with_capacity(capacity); - NodeSlab { nodes } - } -} -impl Index for NodeSlab { - type Output = Option; - fn index(&self, index: usize) -> &Self::Output { - &self.nodes[index] - } -} - -impl IndexMut for NodeSlab { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - if index >= self.nodes.capacity() * 3 { - panic!("Trying to mutate an element way too far out of bounds"); - } - - if index + 1 > self.nodes.len() { - self.nodes.resize_with(index + 1, || None); - } - &mut self.nodes[index] - } -} diff --git a/packages/web/src/olddom.rs b/packages/web/src/olddom.rs index 364f1a1d7..8654ced90 100644 --- a/packages/web/src/olddom.rs +++ b/packages/web/src/olddom.rs @@ -787,3 +787,71 @@ fn event_name_from_typ(typ: &str) -> &'static str { } } } + + +//! This module provides a mirror of the VirtualDOM Element Slab using a Vector. + +use std::ops::{Index, IndexMut}; +use web_sys::Node; + +pub(crate) struct NodeSlab { + nodes: Vec>, +} + +impl NodeSlab { + pub fn new(capacity: usize) -> NodeSlab { + let nodes = Vec::with_capacity(capacity); + NodeSlab { nodes } + } +} +impl Index for NodeSlab { + type Output = Option; + fn index(&self, index: usize) -> &Self::Output { + &self.nodes[index] + } +} + +impl IndexMut for NodeSlab { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + if index >= self.nodes.capacity() * 3 { + panic!("Trying to mutate an element way too far out of bounds"); + } + + if index + 1 > self.nodes.len() { + self.nodes.resize_with(index + 1, || None); + } + &mut self.nodes[index] + } +} + + +#[derive(Debug, Default)] +struct Stack { + list: Vec, +} + +impl Stack { + #[inline] + fn with_capacity(cap: usize) -> Self { + Stack { + list: Vec::with_capacity(cap), + } + } + + #[inline] + fn push(&mut self, node: Node) { + self.list.push(node); + } + + #[inline] + fn pop(&mut self) -> Node { + self.list.pop().unwrap() + } + + fn top(&self) -> &Node { + match self.list.last() { + Some(a) => a, + None => panic!("Called 'top' of an empty stack, make sure to push the root first"), + } + } +} diff --git a/packages/web/src/rehydrate.rs b/packages/web/src/rehydrate.rs index 25b126036..b62cba753 100644 --- a/packages/web/src/rehydrate.rs +++ b/packages/web/src/rehydrate.rs @@ -118,7 +118,7 @@ impl WebsysDom { // we cant have the last node be text let mut last_node_was_text = false; for child in vel.children { - self.rehydrate_single(nodes, place, dom, &child, &mut last_node_was_text)?; + self.rehydrate_single(nodes, place, dom, child, &mut last_node_was_text)?; } place.pop(); @@ -146,7 +146,7 @@ impl WebsysDom { VNode::Fragment(el) => { for el in el.children { - self.rehydrate_single(nodes, place, dom, &el, last_node_was_text)?; + self.rehydrate_single(nodes, place, dom, el, last_node_was_text)?; } } diff --git a/packages/web/src/ric_raf.rs b/packages/web/src/ric_raf.rs index 29c97bcba..2ac0668b9 100644 --- a/packages/web/src/ric_raf.rs +++ b/packages/web/src/ric_raf.rs @@ -68,8 +68,7 @@ impl RafLoop { let ric_fn = self.ric_closure.as_ref().dyn_ref::().unwrap(); let _cb_id: u32 = self.window.request_idle_callback(ric_fn).unwrap(); let deadline = self.ric_receiver.recv().await.unwrap(); - let deadline = TimeoutFuture::new(deadline); - deadline + TimeoutFuture::new(deadline) } pub async fn wait_for_raf(&self) {