Web works with the js structure

This commit is contained in:
Jonathan Kelley committed 2024-03-05 11:16:34 -08:00
1 parent 16b38e339d
commit b751674354
16 files changed
+470 -325

No files matched your search

Generated
-2
View File
@@ -8527,7 +8527,6 @@ dependencies = [
[[package]] [[package]]
name = "sledgehammer_bindgen" name = "sledgehammer_bindgen"
version = "0.4.0" version = "0.4.0"
source = "git+https://github.com/ealmloff/sledgehammer_bindgen#6f90290e1655014f22623eb8b2ea41fb098e1164"
dependencies = [ dependencies = [
"sledgehammer_bindgen_macro", "sledgehammer_bindgen_macro",
"wasm-bindgen", "wasm-bindgen",
@@ -8536,7 +8535,6 @@ dependencies = [
[[package]] [[package]]
name = "sledgehammer_bindgen_macro" name = "sledgehammer_bindgen_macro"
version = "0.4.0" version = "0.4.0"
source = "git+https://github.com/ealmloff/sledgehammer_bindgen#6f90290e1655014f22623eb8b2ea41fb098e1164"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.51", "syn 2.0.51",
+2 -1
View File
@@ -17,7 +17,8 @@ web-sys = { version = "0.3.56", optional = true, features = [
"Element", "Element",
"Node", "Node",
] } ] }
sledgehammer_bindgen = { git = "https://github.com/ealmloff/sledgehammer_bindgen", default-features = false, optional = true } sledgehammer_bindgen = { path = "../../../Tinkering/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 } sledgehammer_utils = { version = "0.2", optional = true }
serde = { version = "1.0", features = ["derive"], optional = true } serde = { version = "1.0", features = ["derive"], optional = true }
+3 -2
View File
@@ -20,8 +20,9 @@ fn main() {
// Otherwise, generate the bindings and write the new hash to disk // Otherwise, generate the bindings and write the new hash to disk
// Generate the bindings for both native and web // Generate the bindings for both native and web
gen_bindings("interpreter_native", "native"); gen_bindings("common", "common");
gen_bindings("interpreter_web", "web"); gen_bindings("native", "native");
gen_bindings("core", "core");
std::fs::write("src/js/hash.txt", hash.to_string()).unwrap(); std::fs::write("src/js/hash.txt", hash.to_string()).unwrap();
} }
+99
View File
@@ -0,0 +1,99 @@
// src/ts/set_attribute.ts
function setAttributeInner(node, field, value, ns) {
if (ns === "style") {
node.style.setProperty(field, value);
return;
}
if (!!ns) {
node.setAttributeNS(ns, field, value);
return;
}
switch (field) {
case "value":
if (node.value !== 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:
if (!truthy(value) && isBoolAttr(field)) {
node.removeAttribute(field);
} else {
node.setAttribute(field, value);
}
}
}
var truthy = function(val) {
return val === "true" || val === true;
};
var isBoolAttr = function(field) {
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;
}
};
// src/ts/form.ts
function retrieveFormValues(form) {
const formData = new FormData(form);
const contents = {};
formData.forEach((value, key) => {
if (contents[key]) {
contents[key] += "," + value;
} else {
contents[key] = value;
}
});
return {
valid: form.checkValidity(),
values: contents
};
}
export {
setAttributeInner,
retrieveFormValues
};
@@ -1,81 +1,3 @@
// src/ts/interpreter_core.ts
class Interpreter {
global;
local;
root;
handler;
nodes;
stack;
templates;
constructor(root, handler) {
this.handler = handler;
this.initialize(root);
}
initialize(root, handler = null) {
this.global = {};
this.local = {};
this.root = root;
this.nodes = [root];
this.stack = [root];
this.templates = {};
if (handler) {
this.handler = handler;
}
}
createListener(event_name, element, bubbles) {
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, event_name, bubbles) {
if (bubbles) {
this.removeBubblingListener(event_name);
} else {
this.removeNonBubblingListener(element, event_name);
}
}
removeBubblingListener(event_name) {
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, event_name) {
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) {
const id = element.getAttribute("data-dioxus-id");
delete this.local[id];
}
getNode(id) {
return this.nodes[id];
}
appendChildren(id, many) {
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]);
}
}
}
// src/ts/set_attribute.ts // src/ts/set_attribute.ts
function setAttributeInner(node, field, value, ns) { function setAttributeInner(node, field, value, ns) {
if (ns === "style") { if (ns === "style") {
@@ -156,13 +78,84 @@ var isBoolAttr = function(field) {
} }
}; };
// src/ts/interpreter_web.ts // src/ts/core.ts
class PlatformInterpreter extends Interpreter { class BaseInterpreter {
global;
local;
root;
handler;
nodes;
stack;
templates;
m; m;
constructor(root, handler) { constructor(root, handler) {
super(root, handler); this.handler = handler;
this.initialize(root);
} }
LoadChild(ptr, len) { initialize(root, handler = null) {
this.global = {};
this.local = {};
this.root = root;
this.nodes = [root];
this.stack = [root];
this.templates = {};
if (handler) {
this.handler = handler;
}
}
createListener(event_name, element, bubbles) {
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, event_name, bubbles) {
if (bubbles) {
this.removeBubblingListener(event_name);
} else {
this.removeNonBubblingListener(element, event_name);
}
}
removeBubblingListener(event_name) {
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, event_name) {
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) {
const id = element.getAttribute("data-dioxus-id");
delete this.local[id];
}
getNode(id) {
return this.nodes[id];
}
appendChildren(id, many) {
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]);
}
}
loadChild(ptr, len) {
let node = this.stack[this.stack.length - 1]; let node = this.stack[this.stack.length - 1];
let ptr_end = ptr + len; let ptr_end = ptr + len;
for (;ptr < ptr_end; ptr++) { for (;ptr < ptr_end; ptr++) {
@@ -207,8 +200,10 @@ class PlatformInterpreter extends Interpreter {
currentNode = treeWalker.nextNode(); currentNode = treeWalker.nextNode();
} }
} }
setAttributeInner(node, field, value, ns) {
setAttributeInner(node, field, value, ns);
}
} }
export { export {
setAttributeInner, BaseInterpreter
PlatformInterpreter
}; };
+1 -1
View File
@@ -1 +1 @@
3599957386864841107 7906913262979346544
+133 -84
View File
@@ -1,5 +1,85 @@
// src/ts/interpreter_core.ts // src/ts/set_attribute.ts
class Interpreter { function setAttributeInner(node, field, value, ns) {
if (ns === "style") {
node.style.setProperty(field, value);
return;
}
if (!!ns) {
node.setAttributeNS(ns, field, value);
return;
}
switch (field) {
case "value":
if (node.value !== 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:
if (!truthy(value) && isBoolAttr(field)) {
node.removeAttribute(field);
} else {
node.setAttribute(field, value);
}
}
}
var truthy = function(val) {
return val === "true" || val === true;
};
var isBoolAttr = function(field) {
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;
}
};
// src/ts/core.ts
class BaseInterpreter {
global; global;
local; local;
root; root;
@@ -7,6 +87,7 @@ class Interpreter {
nodes; nodes;
stack; stack;
templates; templates;
m;
constructor(root, handler) { constructor(root, handler) {
this.handler = handler; this.handler = handler;
this.initialize(root); this.initialize(root);
@@ -74,6 +155,54 @@ class Interpreter {
root.appendChild(els[k]); root.appendChild(els[k]);
} }
} }
loadChild(ptr, len) {
let node = this.stack[this.stack.length - 1];
let ptr_end = ptr + len;
for (;ptr < ptr_end; ptr++) {
let end = this.m.getUint8(ptr);
for (node = node.firstChild;end > 0; end--) {
node = node.nextSibling;
}
}
return node;
}
saveTemplate(nodes, tmpl_id) {
this.templates[tmpl_id] = nodes;
}
hydrateRoot(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])];
this.nodes[id] = hydrateNode;
if (split.length > 1) {
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;
}
currentNode = treeWalker.nextNode();
}
}
setAttributeInner(node, field, value, ns) {
setAttributeInner(node, field, value, ns);
}
} }
// src/ts/form.ts // src/ts/form.ts
@@ -291,87 +420,7 @@ var serializeDragEvent = function(event) {
}; };
}; };
// src/ts/set_attribute.ts // src/ts/native.ts
function setAttributeInner(node, field, value, ns) {
if (ns === "style") {
node.style.setProperty(field, value);
return;
}
if (!!ns) {
node.setAttributeNS(ns, field, value);
return;
}
switch (field) {
case "value":
if (node.value !== 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:
if (!truthy(value) && isBoolAttr(field)) {
node.removeAttribute(field);
} else {
node.setAttribute(field, value);
}
}
}
var truthy = function(val) {
return val === "true" || val === true;
};
var isBoolAttr = function(field) {
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;
}
};
// src/ts/interpreter_native.ts
var targetId = function(target) { var targetId = function(target) {
if (!(target instanceof Node)) { if (!(target instanceof Node)) {
return null; return null;
@@ -390,7 +439,7 @@ var targetId = function(target) {
return parseInt(realId); return parseInt(realId);
}; };
class PlatformInterpreter extends Interpreter { class PlatformInterpreter extends BaseInterpreter {
intercept_link_redirects; intercept_link_redirects;
ipc; ipc;
liveview; liveview;
+6 -3
View File
@@ -2,8 +2,11 @@
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")] #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")] #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
pub static INTERPRETER_JS: &str = include_str!("./js/native.js"); /// The base class that the JS channel will extend
pub static COMMON_JS: &str = include_str!("./js/web.js"); pub static INTERPRETER_JS: &str = include_str!("./js/core.js");
/// The code explicitly for desktop/liveview that bridges the eval gap between the two
pub static NATIVE_JS: &str = include_str!("./js/native.js");
#[cfg(all(feature = "binary-protocol", feature = "sledgehammer"))] #[cfg(all(feature = "binary-protocol", feature = "sledgehammer"))]
mod write_native_mutations; mod write_native_mutations;
@@ -25,7 +28,7 @@ pub mod minimal_bindings {
/// Some useful snippets that we use to share common functionality between the different platforms we support. /// 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 /// This maintains some sort of consistency between web, desktop, and liveview
#[wasm_bindgen(module = "/src/js/web.js")] #[wasm_bindgen(module = "/src/js/common.js")]
extern "C" { extern "C" {
/// Set the attribute of the node /// Set the attribute of the node
pub fn setAttributeInner(node: JsValue, name: &str, value: JsValue, ns: Option<&str>); pub fn setAttributeInner(node: JsValue, name: &str, value: JsValue, ns: Option<&str>);
+2
View File
@@ -0,0 +1,2 @@
export { setAttributeInner } from "./set_attribute";
export { retrieveFormValues } from "./form";
@@ -1,9 +1,11 @@
// The root interpreter class that holds state about the mapping between DOM and VirtualDom // 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 // This always lives in the JS side of things, and is extended by the native and web interpreters
import { setAttributeInner } from "./set_attribute";
export type NodeId = number; export type NodeId = number;
export class Interpreter { export class BaseInterpreter {
// non bubbling events listen at the element the listener was created at // non bubbling events listen at the element the listener was created at
global: { global: {
[key: string]: { active: number, callback: EventListener } [key: string]: { active: number, callback: EventListener }
@@ -23,6 +25,9 @@ export class Interpreter {
[key: string]: Node[] [key: string]: Node[]
}; };
// sledgehammer is generating this...
m: any;
constructor(root: HTMLElement, handler: EventListener) { constructor(root: HTMLElement, handler: EventListener) {
this.handler = handler; this.handler = handler;
this.initialize(root); this.initialize(root);
@@ -100,5 +105,72 @@ export class Interpreter {
root.appendChild(els[k]); root.appendChild(els[k]);
} }
} }
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 = this.m.getUint8(ptr);
for (node = node.firstChild; end > 0; end--) {
node = node.nextSibling;
}
}
return node;
}
saveTemplate(nodes: HTMLElement[], 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] as HTMLElement;
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;
}
currentNode = treeWalker.nextNode();
}
}
setAttributeInner(node: HTMLElement, field: string, value: string, ns: string) {
setAttributeInner(node, field, value, ns);
}
} }
@@ -1,79 +0,0 @@
// 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 } from "./interpreter_core";
export { setAttributeInner } from "./set_attribute";
export class PlatformInterpreter extends Interpreter {
m: any;
constructor(root: HTMLElement, 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 = this.m.getUint8(ptr);
for (node = node.firstChild; end > 0; end--) {
node = node.nextSibling;
}
}
return node;
}
saveTemplate(nodes: HTMLElement[], 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] as HTMLElement;
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;
}
currentNode = treeWalker.nextNode();
}
}
}
@@ -3,11 +3,11 @@
// This file lives on the renderer, not the host. It's basically a polyfill over functionality that the host can't // This file lives on the renderer, not the host. It's basically a polyfill over functionality that the host can't
// provide since it doesn't have access to the dom. // provide since it doesn't have access to the dom.
import { Interpreter, NodeId } from "./interpreter_core"; import { BaseInterpreter, NodeId } from "./core";
import { SerializedEvent, serializeEvent } from "./serialize"; import { SerializedEvent, serializeEvent } from "./serialize";
import { setAttributeInner } from "./set_attribute"; import { setAttributeInner } from "./set_attribute";
export class PlatformInterpreter extends Interpreter { export class PlatformInterpreter extends BaseInterpreter {
intercept_link_redirects: boolean; intercept_link_redirects: boolean;
ipc: any; ipc: any;
+64 -60
View File
@@ -1,32 +1,54 @@
#[cfg(feature = "webonly")] #[cfg(feature = "webonly")]
use web_sys::Node; use web_sys::Node;
#[cfg(feature = "webonly")]
use wasm_bindgen::prelude::wasm_bindgen;
use sledgehammer_bindgen::bindgen;
/// Combine the interpreter class with the sledgehammer_bindgen generated methods.
pub fn native_js() -> String {
format!("{}\n{}", include_str!("./js/native.js"), GENERATED_JS,)
}
pub const SLEDGEHAMMER_JS: &str = GENERATED_JS; pub const SLEDGEHAMMER_JS: &str = GENERATED_JS;
#[bindgen(module)] #[cfg(feature = "webonly")]
#[wasm_bindgen::prelude::wasm_bindgen]
extern "C" {
pub type BaseInterpreter;
#[wasm_bindgen(method)]
pub fn initialize(this: &BaseInterpreter, root: Node, handler: &js_sys::Function);
#[wasm_bindgen(method, js_name = "saveTemplate")]
pub fn save_template(this: &BaseInterpreter, nodes: Vec<Node>, tmpl_id: u16);
#[wasm_bindgen(method)]
pub fn hydrate(this: &BaseInterpreter, ids: Vec<u32>);
#[wasm_bindgen(method, js_name = "getNode")]
pub fn get_node(this: &BaseInterpreter, id: u32) -> Node;
}
// Note that this impl is for the sledgehammer interpreter to allow us dropping down to the base interpreter
// During hydration and initialization we need to the base interpreter methods
#[cfg(feature = "webonly")]
impl Interpreter {
/// Convert the interpreter to its baseclass, giving
pub fn base(&self) -> &BaseInterpreter {
use wasm_bindgen::prelude::JsCast;
&self.js_channel().unchecked_ref()
}
}
#[sledgehammer_bindgen::bindgen(module)]
mod js { mod js {
// Extend the web base class
const BASE: &str = "./src/js/core.js";
/// The interpreter extends the core interpreter which contains the state for the interpreter along with some functions that all platforms use like `AppendChildren`. /// The interpreter extends the core interpreter which contains the state for the interpreter along with some functions that all platforms use like `AppendChildren`.
#[extends(PlatformInterpreter)] #[extends(BaseInterpreter)]
pub struct Interpreter; pub struct Interpreter;
fn mount_to_root() { fn mount_to_root() {
"{this.AppendChildren(this.root, this.stack.length-1);}" "{this.appendChildren(this.root, this.stack.length-1);}"
} }
fn push_root(root: u32) { fn push_root(root: u32) {
"{this.stack.push(this.nodes[$root$]);}" "{this.stack.push(this.nodes[$root$]);}"
} }
fn append_children(id: u32, many: u16) { fn append_children(id: u32, many: u16) {
"{this.AppendChildren($id$, $many$);}" "{this.appendChildren($id$, $many$);}"
} }
fn pop_root() { fn pop_root() {
"{this.stack.pop();}" "{this.stack.pop();}"
@@ -93,11 +115,11 @@ mod js {
}"# }"#
} }
fn assign_id(ptr: u32, len: u8, id: u32) { fn assign_id(ptr: u32, len: u8, id: u32) {
"{this.nodes[$id$] = this.LoadChild($ptr$, $len$);}" "{this.nodes[$id$] = this.loadChild($ptr$, $len$);}"
} }
fn hydrate_text(ptr: u32, len: u8, value: &str, id: u32) { fn hydrate_text(ptr: u32, len: u8, value: &str, id: u32) {
r#"{ r#"{
let node = this.LoadChild($ptr$, $len$); let node = this.loadChild($ptr$, $len$);
if (node.nodeType == node.TEXT_NODE) { if (node.nodeType == node.TEXT_NODE) {
node.textContent = value; node.textContent = value;
} else { } else {
@@ -109,20 +131,13 @@ mod js {
}"# }"#
} }
fn replace_placeholder(ptr: u32, len: u8, n: u16) { fn replace_placeholder(ptr: u32, len: u8, n: u16) {
"{this.els = this.stack.splice(this.stack.length - $n$); let node = this.LoadChild($ptr$, $len$); node.replaceWith(...this.els);}" "{this.els = this.stack.splice(this.stack.length - $n$); let node = this.loadChild($ptr$, $len$); node.replaceWith(...this.els);}"
} }
fn load_template(tmpl_id: u16, index: u16, id: u32) { fn load_template(tmpl_id: u16, index: u16, id: u32) {
"{let node = this.templates[$tmpl_id$][$index$].cloneNode(true); this.nodes[$id$] = node; this.stack.push(node);}" "{let node = this.templates[$tmpl_id$][$index$].cloneNode(true); this.nodes[$id$] = node; this.stack.push(node);}"
} }
/* #[cfg(feature = "binary-protocol")]
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) { fn append_children_to_top(many: u16) {
"{ "{
let root = this.stack[this.stack.length-many-1]; let root = this.stack[this.stack.length-many-1];
@@ -130,23 +145,35 @@ mod js {
for (let k = 0; k < many; k++) { for (let k = 0; k < many; k++) {
root.appendChild(this.els[k]); root.appendChild(this.els[k]);
} }
}" }"
} }
#[cfg(feature = "binary-protocol")]
fn set_top_attribute(field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) { fn set_top_attribute(field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) {
"{this.setAttributeInner(this.stack[this.stack.length-1], $field$, $value$, $ns$);}" "{this.setAttributeInner(this.stack[this.stack.length-1], $field$, $value$, $ns$);}"
} }
#[cfg(feature = "binary-protocol")]
fn add_placeholder() { fn add_placeholder() {
"{let node = document.createElement('pre'); node.hidden = true; this.stack.push(node);}" "{let node = document.createElement('pre'); node.hidden = true; this.stack.push(node);}"
} }
#[cfg(feature = "binary-protocol")]
fn create_element(element: &'static str<u8, el>) { fn create_element(element: &'static str<u8, el>) {
"{this.stack.push(document.createElement($element$))}" "{this.stack.push(document.createElement($element$))}"
} }
#[cfg(feature = "binary-protocol")]
fn create_element_ns(element: &'static str<u8, el>, ns: &'static str<u8, namespace>) { fn create_element_ns(element: &'static str<u8, el>, ns: &'static str<u8, namespace>) {
"{this.stack.push(document.createElementNS($ns$, $element$))}" "{this.stack.push(document.createElementNS($ns$, $element$))}"
} }
#[cfg(feature = "binary-protocol")]
fn add_templates(tmpl_id: u16, len: u16) { fn add_templates(tmpl_id: u16, len: u16) {
"{this.templates[$tmpl_id$] = this.stack.splice(this.stack.length-$len$);}" "{this.templates[$tmpl_id$] = this.stack.splice(this.stack.length-$len$);}"
} }
#[cfg(feature = "binary-protocol")]
fn foreign_event_listener(event: &str<u8, evt>, id: u32, bubbles: u8) { fn foreign_event_listener(event: &str<u8, evt>, id: u32, bubbles: u8) {
r#" r#"
bubbles = bubbles == 1; bubbles = bubbles == 1;
@@ -175,12 +202,18 @@ mod js {
}); });
}"# }"#
} }
/// Assign the ID
#[cfg(feature = "binary-protocol")]
fn assign_id_ref(array: &[u8], id: u32) { fn assign_id_ref(array: &[u8], id: u32) {
"{this.nodes[$id$] = this.LoadChild($array$);}" "{this.nodes[$id$] = this.loadChild($array$);}"
} }
/// The coolest ID ever!
#[cfg(feature = "binary-protocol")]
fn hydrate_text_ref(array: &[u8], value: &str, id: u32) { fn hydrate_text_ref(array: &[u8], value: &str, id: u32) {
r#"{ r#"{
let node = this.LoadChild($array$); let node = this.loadChild($array$);
if (node.nodeType == node.TEXT_NODE) { if (node.nodeType == node.TEXT_NODE) {
node.textContent = value; node.textContent = value;
} else { } else {
@@ -191,38 +224,9 @@ mod js {
this.nodes[$id$] = node; this.nodes[$id$] = node;
}"# }"#
} }
#[cfg(feature = "binary-protocol")]
fn replace_placeholder_ref(array: &[u8], n: u16) { fn replace_placeholder_ref(array: &[u8], n: u16) {
"{this.els = this.stack.splice(this.stack.length - $n$); let node = this.LoadChild($array$); node.replaceWith(...this.els);}" "{this.els = this.stack.splice(this.stack.length - $n$); let node = this.loadChild($array$); node.replaceWith(...this.els);}"
}
}
/// Extensions to the interpreter that are specific to the web platform.
#[cfg(feature = "webonly")]
#[wasm_bindgen(module = "src/js/web.js")]
extern "C" {
pub type WebInterpreter;
#[wasm_bindgen(method)]
pub fn initialize(this: &WebInterpreter, root: Node, handler: &js_sys::Function);
#[wasm_bindgen(method, js_name = "saveTemplate")]
pub fn save_template(this: &WebInterpreter, nodes: Vec<Node>, tmpl_id: u16);
#[wasm_bindgen(method)]
pub fn hydrate(this: &WebInterpreter, ids: Vec<u32>);
#[wasm_bindgen(method, js_name = "getNode")]
pub fn get_node(this: &WebInterpreter, id: u32) -> Node;
}
#[cfg(feature = "webonly")]
type PlatformInterpreter = WebInterpreter;
#[cfg(feature = "webonly")]
impl Interpreter {
/// Convert the interpreter to a web interpreter, enabling methods like hydrate and save_template.
pub fn as_web(&self) -> &WebInterpreter {
use wasm_bindgen::prelude::JsCast;
&self.js_channel().unchecked_ref()
} }
} }
+2 -2
View File
@@ -8,7 +8,7 @@
use dioxus_core::ElementId; use dioxus_core::ElementId;
use dioxus_html::PlatformEventData; use dioxus_html::PlatformEventData;
use dioxus_interpreter_js::{unified_bindings::Interpreter, WebInterpreter}; use dioxus_interpreter_js::unified_bindings::Interpreter;
use futures_channel::mpsc; use futures_channel::mpsc;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use wasm_bindgen::{closure::Closure, JsCast}; use wasm_bindgen::{closure::Closure, JsCast};
@@ -112,7 +112,7 @@ impl WebsysDom {
} }
})); }));
let _interpreter: &WebInterpreter = interpreter.as_web(); let _interpreter = interpreter.base();
_interpreter.initialize( _interpreter.initialize(
root.clone().unchecked_into(), root.clone().unchecked_into(),
handler.as_ref().unchecked_ref(), handler.as_ref().unchecked_ref(),
+2 -2
View File
@@ -65,7 +65,7 @@ impl WebsysDom {
#[cfg(feature = "mounted")] #[cfg(feature = "mounted")]
fn flush_queued_mounted_events(&mut self) { fn flush_queued_mounted_events(&mut self) {
for id in self.queued_mounted_events.drain(..) { for id in self.queued_mounted_events.drain(..) {
let node = self.interpreter.as_web().get_node(id.0 as u32); let node = self.interpreter.base().get_node(id.0 as u32);
if let Some(element) = node.dyn_ref::<web_sys::Element>() { if let Some(element) = node.dyn_ref::<web_sys::Element>() {
let _ = self.event_channel.unbounded_send(UiEvent { let _ = self.event_channel.unbounded_send(UiEvent {
name: "mounted".to_string(), name: "mounted".to_string(),
@@ -92,7 +92,7 @@ impl WriteMutations for WebsysDom {
self.templates self.templates
.insert(template.name.to_owned(), self.max_template_id); .insert(template.name.to_owned(), self.max_template_id);
self.interpreter self.interpreter
.as_web() .base()
.save_template(roots, self.max_template_id); .save_template(roots, self.max_template_id);
self.max_template_id += 1 self.max_template_id += 1
} }
+2 -2
View File
@@ -22,7 +22,7 @@ impl WebsysDom {
// Recursively rehydrate the dom from the VirtualDom // Recursively rehydrate the dom from the VirtualDom
self.rehydrate_scope(root_scope, dom, &mut ids, &mut to_mount)?; self.rehydrate_scope(root_scope, dom, &mut ids, &mut to_mount)?;
self.interpreter.as_web().hydrate(ids); self.interpreter.base().hydrate(ids);
#[cfg(feature = "mounted")] #[cfg(feature = "mounted")]
for id in to_mount { for id in to_mount {
@@ -168,7 +168,7 @@ impl WriteMutations for OnlyWriteTemplates<'_> {
.insert(template.name.to_owned(), self.0.max_template_id); .insert(template.name.to_owned(), self.0.max_template_id);
self.0 self.0
.interpreter .interpreter
.as_web() .base()
.save_template(roots, self.0.max_template_id); .save_template(roots, self.0.max_template_id);
self.0.max_template_id += 1 self.0.max_template_id += 1
} }