wip: some ideas
This commit is contained in:
1 parent
583fdfa561
commit
05c909f320
21 files changed
+662
-1112
No files matched your search
@@ -0,0 +1,292 @@
|
||||
/// Wasm-bindgen has a performance option to intern commonly used phrases
|
||||
/// This saves the decoding cost, making the interaction of Rust<->JS more performant.
|
||||
/// We intern all the HTML tags and attributes, making most operations much faster.
|
||||
///
|
||||
/// Interning takes about 1ms at the start of the app, but saves a *ton* of time later on.
|
||||
pub fn intern_cache() {
|
||||
let cached_words = [
|
||||
// All the HTML Tags
|
||||
"a",
|
||||
"abbr",
|
||||
"address",
|
||||
"area",
|
||||
"article",
|
||||
"aside",
|
||||
"audio",
|
||||
"b",
|
||||
"base",
|
||||
"bdi",
|
||||
"bdo",
|
||||
"big",
|
||||
"blockquote",
|
||||
"body",
|
||||
"br",
|
||||
"button",
|
||||
"canvas",
|
||||
"caption",
|
||||
"cite",
|
||||
"code",
|
||||
"col",
|
||||
"colgroup",
|
||||
"command",
|
||||
"data",
|
||||
"datalist",
|
||||
"dd",
|
||||
"del",
|
||||
"details",
|
||||
"dfn",
|
||||
"dialog",
|
||||
"div",
|
||||
"dl",
|
||||
"dt",
|
||||
"em",
|
||||
"embed",
|
||||
"fieldset",
|
||||
"figcaption",
|
||||
"figure",
|
||||
"footer",
|
||||
"form",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"head",
|
||||
"header",
|
||||
"hr",
|
||||
"html",
|
||||
"i",
|
||||
"iframe",
|
||||
"img",
|
||||
"input",
|
||||
"ins",
|
||||
"kbd",
|
||||
"keygen",
|
||||
"label",
|
||||
"legend",
|
||||
"li",
|
||||
"link",
|
||||
"main",
|
||||
"map",
|
||||
"mark",
|
||||
"menu",
|
||||
"menuitem",
|
||||
"meta",
|
||||
"meter",
|
||||
"nav",
|
||||
"noscript",
|
||||
"object",
|
||||
"ol",
|
||||
"optgroup",
|
||||
"option",
|
||||
"output",
|
||||
"p",
|
||||
"param",
|
||||
"picture",
|
||||
"pre",
|
||||
"progress",
|
||||
"q",
|
||||
"rp",
|
||||
"rt",
|
||||
"ruby",
|
||||
"s",
|
||||
"samp",
|
||||
"script",
|
||||
"section",
|
||||
"select",
|
||||
"small",
|
||||
"source",
|
||||
"span",
|
||||
"strong",
|
||||
"style",
|
||||
"sub",
|
||||
"summary",
|
||||
"sup",
|
||||
"table",
|
||||
"tbody",
|
||||
"td",
|
||||
"textarea",
|
||||
"tfoot",
|
||||
"th",
|
||||
"thead",
|
||||
"time",
|
||||
"title",
|
||||
"tr",
|
||||
"track",
|
||||
"u",
|
||||
"ul",
|
||||
"var",
|
||||
"video",
|
||||
"wbr",
|
||||
// All the event handlers
|
||||
"Attribute",
|
||||
"accept",
|
||||
"accept-charset",
|
||||
"accesskey",
|
||||
"action",
|
||||
"alt",
|
||||
"async",
|
||||
"autocomplete",
|
||||
"autofocus",
|
||||
"autoplay",
|
||||
"charset",
|
||||
"checked",
|
||||
"cite",
|
||||
"class",
|
||||
"cols",
|
||||
"colspan",
|
||||
"content",
|
||||
"contenteditable",
|
||||
"controls",
|
||||
"coords",
|
||||
"data",
|
||||
"data-*",
|
||||
"datetime",
|
||||
"default",
|
||||
"defer",
|
||||
"dir",
|
||||
"dirname",
|
||||
"disabled",
|
||||
"download",
|
||||
"draggable",
|
||||
"enctype",
|
||||
"for",
|
||||
"form",
|
||||
"formaction",
|
||||
"headers",
|
||||
"height",
|
||||
"hidden",
|
||||
"high",
|
||||
"href",
|
||||
"hreflang",
|
||||
"http-equiv",
|
||||
"id",
|
||||
"ismap",
|
||||
"kind",
|
||||
"label",
|
||||
"lang",
|
||||
"list",
|
||||
"loop",
|
||||
"low",
|
||||
"max",
|
||||
"maxlength",
|
||||
"media",
|
||||
"method",
|
||||
"min",
|
||||
"multiple",
|
||||
"muted",
|
||||
"name",
|
||||
"novalidate",
|
||||
"onabort",
|
||||
"onafterprint",
|
||||
"onbeforeprint",
|
||||
"onbeforeunload",
|
||||
"onblur",
|
||||
"oncanplay",
|
||||
"oncanplaythrough",
|
||||
"onchange",
|
||||
"onclick",
|
||||
"oncontextmenu",
|
||||
"oncopy",
|
||||
"oncuechange",
|
||||
"oncut",
|
||||
"ondblclick",
|
||||
"ondrag",
|
||||
"ondragend",
|
||||
"ondragenter",
|
||||
"ondragleave",
|
||||
"ondragover",
|
||||
"ondragstart",
|
||||
"ondrop",
|
||||
"ondurationchange",
|
||||
"onemptied",
|
||||
"onended",
|
||||
"onerror",
|
||||
"onfocus",
|
||||
"onhashchange",
|
||||
"oninput",
|
||||
"oninvalid",
|
||||
"onkeydown",
|
||||
"onkeypress",
|
||||
"onkeyup",
|
||||
"onload",
|
||||
"onloadeddata",
|
||||
"onloadedmetadata",
|
||||
"onloadstart",
|
||||
"onmousedown",
|
||||
"onmousemove",
|
||||
"onmouseout",
|
||||
"onmouseover",
|
||||
"onmouseup",
|
||||
"onmousewheel",
|
||||
"onoffline",
|
||||
"ononline",
|
||||
"<body>",
|
||||
"onpageshow",
|
||||
"onpaste",
|
||||
"onpause",
|
||||
"onplay",
|
||||
"onplaying",
|
||||
"<body>",
|
||||
"onprogress",
|
||||
"onratechange",
|
||||
"onreset",
|
||||
"onresize",
|
||||
"onscroll",
|
||||
"onsearch",
|
||||
"onseeked",
|
||||
"onseeking",
|
||||
"onselect",
|
||||
"onstalled",
|
||||
"<body>",
|
||||
"onsubmit",
|
||||
"onsuspend",
|
||||
"ontimeupdate",
|
||||
"ontoggle",
|
||||
"onunload",
|
||||
"onvolumechange",
|
||||
"onwaiting",
|
||||
"onwheel",
|
||||
"open",
|
||||
"optimum",
|
||||
"pattern",
|
||||
"placeholder",
|
||||
"poster",
|
||||
"preload",
|
||||
"readonly",
|
||||
"rel",
|
||||
"required",
|
||||
"reversed",
|
||||
"rows",
|
||||
"rowspan",
|
||||
"sandbox",
|
||||
"scope",
|
||||
"selected",
|
||||
"shape",
|
||||
"size",
|
||||
"sizes",
|
||||
"span",
|
||||
"spellcheck",
|
||||
"src",
|
||||
"srcdoc",
|
||||
"srclang",
|
||||
"srcset",
|
||||
"start",
|
||||
"step",
|
||||
"style",
|
||||
"tabindex",
|
||||
"target",
|
||||
"title",
|
||||
"translate",
|
||||
"type",
|
||||
"usemap",
|
||||
"value",
|
||||
"width",
|
||||
"wrap",
|
||||
];
|
||||
|
||||
for s in cached_words {
|
||||
wasm_bindgen::intern(s);
|
||||
}
|
||||
}
|
||||
+82
-419
@@ -3,449 +3,112 @@
|
||||
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
|
||||
|
||||
use dioxus::prelude::{Context, Properties, VNode};
|
||||
use futures_util::{pin_mut, Stream, StreamExt};
|
||||
|
||||
use fxhash::FxHashMap;
|
||||
use web_sys::{window, Document, Element, Event, Node};
|
||||
// use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
|
||||
use dioxus::virtual_dom::VirtualDom;
|
||||
pub use dioxus_core as dioxus;
|
||||
use dioxus_core::{events::EventTrigger, prelude::FC};
|
||||
use futures_util::{pin_mut, Stream, StreamExt};
|
||||
use fxhash::FxHashMap;
|
||||
use web_sys::{window, Document, Element, Event, Node};
|
||||
|
||||
pub use dioxus_core::prelude;
|
||||
// pub mod interpreter;
|
||||
pub mod new;
|
||||
mod cache;
|
||||
mod new;
|
||||
|
||||
/// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM.
|
||||
/// Under the hood, we leverage WebSys and interact directly with the DOM
|
||||
/// Launches the VirtualDOM from the specified component function.
|
||||
///
|
||||
pub struct WebsysRenderer {
|
||||
internal_dom: VirtualDom,
|
||||
/// This method will block the thread with `spawn_local`
|
||||
pub fn launch<F>(root: FC<()>, config: F)
|
||||
where
|
||||
F: FnOnce(()),
|
||||
{
|
||||
wasm_bindgen_futures::spawn_local(run(root))
|
||||
}
|
||||
|
||||
impl WebsysRenderer {
|
||||
/// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
|
||||
/// See DioxusErrors for more information on how these errors could occour.
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn main() {
|
||||
/// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Run the app to completion, panicing if any error occurs while rendering.
|
||||
/// Pairs well with the wasm_bindgen async handler
|
||||
pub async fn start(root: FC<()>) {
|
||||
Self::new(root).run().await.expect("Virtual DOM failed :(");
|
||||
}
|
||||
pub fn launch_with_props<T, F>(root: FC<T>, root_props: T, config: F)
|
||||
where
|
||||
T: Properties + 'static,
|
||||
F: FnOnce(()),
|
||||
{
|
||||
wasm_bindgen_futures::spawn_local(run_with_props(root, root_props))
|
||||
}
|
||||
|
||||
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
|
||||
///
|
||||
/// This means that the root component must either consumes its own context, or statics are used to generate the page.
|
||||
/// The root component can access things like routing in its context.
|
||||
pub fn new(root: FC<()>) -> Self {
|
||||
Self::new_with_props(root, ())
|
||||
}
|
||||
/// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
|
||||
/// See DioxusErrors for more information on how these errors could occour.
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn main() {
|
||||
/// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Run the app to completion, panicing if any error occurs while rendering.
|
||||
/// Pairs well with the wasm_bindgen async handler
|
||||
pub async fn run(root: FC<()>) {
|
||||
run_with_props(root, ()).await;
|
||||
}
|
||||
|
||||
/// Create a new text-renderer instance from a functional component root.
|
||||
/// Automatically progresses the creation of the VNode tree to completion.
|
||||
///
|
||||
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
|
||||
pub fn new_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) -> Self {
|
||||
Self::from_vdom(VirtualDom::new_with_props(root, root_props))
|
||||
}
|
||||
pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) {
|
||||
let dom = VirtualDom::new_with_props(root, root_props);
|
||||
event_loop(dom).await.expect("Event loop failed");
|
||||
}
|
||||
|
||||
/// Create a new text renderer from an existing Virtual DOM.
|
||||
pub fn from_vdom(dom: VirtualDom) -> Self {
|
||||
Self { internal_dom: dom }
|
||||
}
|
||||
pub async fn event_loop(mut internal_dom: VirtualDom) -> dioxus_core::error::Result<()> {
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
|
||||
use wasm_bindgen::JsCast;
|
||||
let root = prepare_websys_dom();
|
||||
let root_node = root.clone().dyn_into::<Node>().unwrap();
|
||||
|
||||
let root = prepare_websys_dom();
|
||||
let root_node = root.clone().dyn_into::<Node>().unwrap();
|
||||
let mut websys_dom = crate::new::WebsysDom::new(root.clone());
|
||||
|
||||
let mut websys_dom = crate::new::WebsysDom::new(root.clone());
|
||||
websys_dom.stack.push(root_node.clone());
|
||||
websys_dom.stack.push(root_node);
|
||||
|
||||
websys_dom.stack.push(root_node.clone());
|
||||
websys_dom.stack.push(root_node);
|
||||
let mut edits = Vec::new();
|
||||
internal_dom.rebuild(&mut websys_dom, &mut edits)?;
|
||||
websys_dom.process_edits(&mut edits);
|
||||
|
||||
let mut edits = Vec::new();
|
||||
self.internal_dom.rebuild(&mut websys_dom, &mut edits)?;
|
||||
websys_dom.process_edits(&mut edits);
|
||||
log::info!("Going into event loop");
|
||||
loop {
|
||||
let trigger = {
|
||||
let real_queue = websys_dom.wait_for_event();
|
||||
if internal_dom.tasks.is_empty() {
|
||||
log::info!("tasks is empty, waiting for dom event to trigger soemthing");
|
||||
real_queue.await
|
||||
} else {
|
||||
log::info!("tasks is not empty, waiting for either tasks or event system");
|
||||
let task_queue = (&mut internal_dom.tasks).next();
|
||||
|
||||
log::info!("Going into event loop");
|
||||
loop {
|
||||
let trigger = {
|
||||
let real_queue = websys_dom.wait_for_event();
|
||||
if self.internal_dom.tasks.is_empty() {
|
||||
log::info!("tasks is empty, waiting for dom event to trigger soemthing");
|
||||
real_queue.await
|
||||
} else {
|
||||
log::info!("tasks is not empty, waiting for either tasks or event system");
|
||||
let task_queue = (&mut self.internal_dom.tasks).next();
|
||||
pin_mut!(real_queue);
|
||||
pin_mut!(task_queue);
|
||||
|
||||
pin_mut!(real_queue);
|
||||
pin_mut!(task_queue);
|
||||
|
||||
match futures_util::future::select(real_queue, task_queue).await {
|
||||
futures_util::future::Either::Left((trigger, _)) => trigger,
|
||||
futures_util::future::Either::Right((trigger, _)) => trigger,
|
||||
}
|
||||
match futures_util::future::select(real_queue, task_queue).await {
|
||||
futures_util::future::Either::Left((trigger, _)) => trigger,
|
||||
futures_util::future::Either::Right((trigger, _)) => trigger,
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(real_trigger) = trigger {
|
||||
log::info!("event received");
|
||||
|
||||
self.internal_dom.queue_event(real_trigger)?;
|
||||
|
||||
let mut edits = Vec::new();
|
||||
self.internal_dom
|
||||
.progress_with_event(&mut websys_dom, &mut edits)
|
||||
.await?;
|
||||
websys_dom.process_edits(&mut edits);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// should actually never return from this, should be an error, rustc just cant see it
|
||||
Ok(())
|
||||
if let Some(real_trigger) = trigger {
|
||||
log::info!("event received");
|
||||
|
||||
internal_dom.queue_event(real_trigger)?;
|
||||
|
||||
let mut edits = Vec::new();
|
||||
internal_dom
|
||||
.progress_with_event(&mut websys_dom, &mut edits)
|
||||
.await?;
|
||||
websys_dom.process_edits(&mut edits);
|
||||
}
|
||||
}
|
||||
|
||||
// should actually never return from this, should be an error, rustc just cant see it
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepare_websys_dom() -> Element {
|
||||
// Initialize the container on the dom
|
||||
// Hook up the body as the root component to render tinto
|
||||
let window = web_sys::window().expect("should have access to the Window");
|
||||
let document = window
|
||||
web_sys::window()
|
||||
.expect("should have access to the Window")
|
||||
.document()
|
||||
.expect("should have access to the Document");
|
||||
|
||||
// let body = document.body().unwrap();
|
||||
let el = document.get_element_by_id("dioxusroot").unwrap();
|
||||
|
||||
// Build a dummy div
|
||||
// let container: &Element = body.as_ref();
|
||||
// container.set_inner_html("");
|
||||
// container
|
||||
// .append_child(
|
||||
// document
|
||||
// .create_element("div")
|
||||
// .expect("should create element OK")
|
||||
// .as_ref(),
|
||||
// )
|
||||
// .expect("should append child OK");
|
||||
el
|
||||
// container.clone()
|
||||
}
|
||||
|
||||
// Progress the mount of the root component
|
||||
|
||||
// Iterate through the nodes, attaching the closure and sender to the listener
|
||||
// {
|
||||
// let mut remote_sender = sender.clone();
|
||||
// let listener = move || {
|
||||
// let event = EventTrigger::new();
|
||||
// wasm_bindgen_futures::spawn_local(async move {
|
||||
// remote_sender
|
||||
// .send(event)
|
||||
// .await
|
||||
// .expect("Updating receiver failed");
|
||||
// })
|
||||
// };
|
||||
// }
|
||||
|
||||
/// Wasm-bindgen has a performance option to intern commonly used phrases
|
||||
/// This saves the decoding cost, making the interaction of Rust<->JS more performant.
|
||||
/// We intern all the HTML tags and attributes, making most operations much faster.
|
||||
///
|
||||
/// Interning takes about 1ms at the start of the app, but saves a *ton* of time later on.
|
||||
pub fn intern_cache() {
|
||||
let cached_words = [
|
||||
// All the HTML Tags
|
||||
"a",
|
||||
"abbr",
|
||||
"address",
|
||||
"area",
|
||||
"article",
|
||||
"aside",
|
||||
"audio",
|
||||
"b",
|
||||
"base",
|
||||
"bdi",
|
||||
"bdo",
|
||||
"big",
|
||||
"blockquote",
|
||||
"body",
|
||||
"br",
|
||||
"button",
|
||||
"canvas",
|
||||
"caption",
|
||||
"cite",
|
||||
"code",
|
||||
"col",
|
||||
"colgroup",
|
||||
"command",
|
||||
"data",
|
||||
"datalist",
|
||||
"dd",
|
||||
"del",
|
||||
"details",
|
||||
"dfn",
|
||||
"dialog",
|
||||
"div",
|
||||
"dl",
|
||||
"dt",
|
||||
"em",
|
||||
"embed",
|
||||
"fieldset",
|
||||
"figcaption",
|
||||
"figure",
|
||||
"footer",
|
||||
"form",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"head",
|
||||
"header",
|
||||
"hr",
|
||||
"html",
|
||||
"i",
|
||||
"iframe",
|
||||
"img",
|
||||
"input",
|
||||
"ins",
|
||||
"kbd",
|
||||
"keygen",
|
||||
"label",
|
||||
"legend",
|
||||
"li",
|
||||
"link",
|
||||
"main",
|
||||
"map",
|
||||
"mark",
|
||||
"menu",
|
||||
"menuitem",
|
||||
"meta",
|
||||
"meter",
|
||||
"nav",
|
||||
"noscript",
|
||||
"object",
|
||||
"ol",
|
||||
"optgroup",
|
||||
"option",
|
||||
"output",
|
||||
"p",
|
||||
"param",
|
||||
"picture",
|
||||
"pre",
|
||||
"progress",
|
||||
"q",
|
||||
"rp",
|
||||
"rt",
|
||||
"ruby",
|
||||
"s",
|
||||
"samp",
|
||||
"script",
|
||||
"section",
|
||||
"select",
|
||||
"small",
|
||||
"source",
|
||||
"span",
|
||||
"strong",
|
||||
"style",
|
||||
"sub",
|
||||
"summary",
|
||||
"sup",
|
||||
"table",
|
||||
"tbody",
|
||||
"td",
|
||||
"textarea",
|
||||
"tfoot",
|
||||
"th",
|
||||
"thead",
|
||||
"time",
|
||||
"title",
|
||||
"tr",
|
||||
"track",
|
||||
"u",
|
||||
"ul",
|
||||
"var",
|
||||
"video",
|
||||
"wbr",
|
||||
// All the event handlers
|
||||
"Attribute",
|
||||
"accept",
|
||||
"accept-charset",
|
||||
"accesskey",
|
||||
"action",
|
||||
"alt",
|
||||
"async",
|
||||
"autocomplete",
|
||||
"autofocus",
|
||||
"autoplay",
|
||||
"charset",
|
||||
"checked",
|
||||
"cite",
|
||||
"class",
|
||||
"cols",
|
||||
"colspan",
|
||||
"content",
|
||||
"contenteditable",
|
||||
"controls",
|
||||
"coords",
|
||||
"data",
|
||||
"data-*",
|
||||
"datetime",
|
||||
"default",
|
||||
"defer",
|
||||
"dir",
|
||||
"dirname",
|
||||
"disabled",
|
||||
"download",
|
||||
"draggable",
|
||||
"enctype",
|
||||
"for",
|
||||
"form",
|
||||
"formaction",
|
||||
"headers",
|
||||
"height",
|
||||
"hidden",
|
||||
"high",
|
||||
"href",
|
||||
"hreflang",
|
||||
"http-equiv",
|
||||
"id",
|
||||
"ismap",
|
||||
"kind",
|
||||
"label",
|
||||
"lang",
|
||||
"list",
|
||||
"loop",
|
||||
"low",
|
||||
"max",
|
||||
"maxlength",
|
||||
"media",
|
||||
"method",
|
||||
"min",
|
||||
"multiple",
|
||||
"muted",
|
||||
"name",
|
||||
"novalidate",
|
||||
"onabort",
|
||||
"onafterprint",
|
||||
"onbeforeprint",
|
||||
"onbeforeunload",
|
||||
"onblur",
|
||||
"oncanplay",
|
||||
"oncanplaythrough",
|
||||
"onchange",
|
||||
"onclick",
|
||||
"oncontextmenu",
|
||||
"oncopy",
|
||||
"oncuechange",
|
||||
"oncut",
|
||||
"ondblclick",
|
||||
"ondrag",
|
||||
"ondragend",
|
||||
"ondragenter",
|
||||
"ondragleave",
|
||||
"ondragover",
|
||||
"ondragstart",
|
||||
"ondrop",
|
||||
"ondurationchange",
|
||||
"onemptied",
|
||||
"onended",
|
||||
"onerror",
|
||||
"onfocus",
|
||||
"onhashchange",
|
||||
"oninput",
|
||||
"oninvalid",
|
||||
"onkeydown",
|
||||
"onkeypress",
|
||||
"onkeyup",
|
||||
"onload",
|
||||
"onloadeddata",
|
||||
"onloadedmetadata",
|
||||
"onloadstart",
|
||||
"onmousedown",
|
||||
"onmousemove",
|
||||
"onmouseout",
|
||||
"onmouseover",
|
||||
"onmouseup",
|
||||
"onmousewheel",
|
||||
"onoffline",
|
||||
"ononline",
|
||||
"<body>",
|
||||
"onpageshow",
|
||||
"onpaste",
|
||||
"onpause",
|
||||
"onplay",
|
||||
"onplaying",
|
||||
"<body>",
|
||||
"onprogress",
|
||||
"onratechange",
|
||||
"onreset",
|
||||
"onresize",
|
||||
"onscroll",
|
||||
"onsearch",
|
||||
"onseeked",
|
||||
"onseeking",
|
||||
"onselect",
|
||||
"onstalled",
|
||||
"<body>",
|
||||
"onsubmit",
|
||||
"onsuspend",
|
||||
"ontimeupdate",
|
||||
"ontoggle",
|
||||
"onunload",
|
||||
"onvolumechange",
|
||||
"onwaiting",
|
||||
"onwheel",
|
||||
"open",
|
||||
"optimum",
|
||||
"pattern",
|
||||
"placeholder",
|
||||
"poster",
|
||||
"preload",
|
||||
"readonly",
|
||||
"rel",
|
||||
"required",
|
||||
"reversed",
|
||||
"rows",
|
||||
"rowspan",
|
||||
"sandbox",
|
||||
"scope",
|
||||
"selected",
|
||||
"shape",
|
||||
"size",
|
||||
"sizes",
|
||||
"span",
|
||||
"spellcheck",
|
||||
"src",
|
||||
"srcdoc",
|
||||
"srclang",
|
||||
"srcset",
|
||||
"start",
|
||||
"step",
|
||||
"style",
|
||||
"tabindex",
|
||||
"target",
|
||||
"title",
|
||||
"translate",
|
||||
"type",
|
||||
"usemap",
|
||||
"value",
|
||||
"width",
|
||||
"wrap",
|
||||
];
|
||||
|
||||
for s in cached_words {
|
||||
wasm_bindgen::intern(s);
|
||||
}
|
||||
.expect("should have access to the Document")
|
||||
.get_element_by_id("dioxusroot")
|
||||
.unwrap()
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::{cmp::min, rc::Rc};
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::{Element, Node, Text};
|
||||
|
||||
/// Apply all of the patches to our old root node in order to create the new root node
|
||||
/// that we desire.
|
||||
/// This is usually used after diffing two virtual nodes.
|
||||
pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<(), JsValue> {
|
||||
// pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<ActiveClosures, JsValue> {
|
||||
let root_node: Node = root_node.into();
|
||||
|
||||
let mut cur_node_idx = 0;
|
||||
|
||||
let mut nodes_to_find = HashSet::new();
|
||||
|
||||
for patch in patches {
|
||||
nodes_to_find.insert(patch.node_idx());
|
||||
}
|
||||
|
||||
let mut element_nodes_to_patch = HashMap::new();
|
||||
let mut text_nodes_to_patch = HashMap::new();
|
||||
|
||||
// Closures that were added to the DOM during this patch operation.
|
||||
// let mut active_closures = HashMap::new();
|
||||
|
||||
find_nodes(
|
||||
root_node,
|
||||
&mut cur_node_idx,
|
||||
&mut nodes_to_find,
|
||||
&mut element_nodes_to_patch,
|
||||
&mut text_nodes_to_patch,
|
||||
);
|
||||
|
||||
for patch in patches {
|
||||
let patch_node_idx = patch.node_idx();
|
||||
|
||||
if let Some(element) = element_nodes_to_patch.get(&patch_node_idx) {
|
||||
let new_closures = apply_element_patch(&element, &patch)?;
|
||||
// active_closures.extend(new_closures);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(text_node) = text_nodes_to_patch.get(&patch_node_idx) {
|
||||
apply_text_patch(&text_node, &patch)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
unreachable!("Getting here means we didn't find the element or next node that we were supposed to patch.")
|
||||
}
|
||||
|
||||
// Ok(active_closures)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_nodes(
|
||||
root_node: Node,
|
||||
cur_node_idx: &mut usize,
|
||||
nodes_to_find: &mut HashSet<usize>,
|
||||
element_nodes_to_patch: &mut HashMap<usize, Element>,
|
||||
text_nodes_to_patch: &mut HashMap<usize, Text>,
|
||||
) {
|
||||
if nodes_to_find.len() == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// We use child_nodes() instead of children() because children() ignores text nodes
|
||||
let children = root_node.child_nodes();
|
||||
let child_node_count = children.length();
|
||||
|
||||
// If the root node matches, mark it for patching
|
||||
if nodes_to_find.get(&cur_node_idx).is_some() {
|
||||
match root_node.node_type() {
|
||||
Node::ELEMENT_NODE => {
|
||||
element_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
|
||||
}
|
||||
Node::TEXT_NODE => {
|
||||
text_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
|
||||
}
|
||||
other => unimplemented!("Unsupported root node type: {}", other),
|
||||
}
|
||||
nodes_to_find.remove(&cur_node_idx);
|
||||
}
|
||||
|
||||
*cur_node_idx += 1;
|
||||
|
||||
for i in 0..child_node_count {
|
||||
let node = children.item(i).unwrap();
|
||||
|
||||
match node.node_type() {
|
||||
Node::ELEMENT_NODE => {
|
||||
find_nodes(
|
||||
node,
|
||||
cur_node_idx,
|
||||
nodes_to_find,
|
||||
element_nodes_to_patch,
|
||||
text_nodes_to_patch,
|
||||
);
|
||||
}
|
||||
Node::TEXT_NODE => {
|
||||
if nodes_to_find.get(&cur_node_idx).is_some() {
|
||||
text_nodes_to_patch.insert(*cur_node_idx, node.unchecked_into());
|
||||
}
|
||||
|
||||
*cur_node_idx += 1;
|
||||
}
|
||||
Node::COMMENT_NODE => {
|
||||
// At this time we do not support user entered comment nodes, so if we see a comment
|
||||
// then it was a delimiter created by virtual-dom-rs in order to ensure that two
|
||||
// neighboring text nodes did not get merged into one by the browser. So we skip
|
||||
// over this virtual-dom-rs generated comment node.
|
||||
}
|
||||
_other => {
|
||||
// Ignoring unsupported child node type
|
||||
// TODO: What do we do with this situation? Log a warning?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub type ActiveClosures = HashMap<u32, Vec<DynClosure>>;
|
||||
|
||||
// fn apply_element_patch(node: &Element, patch: &Patch) -> Result<ActiveClosures, JsValue> {
|
||||
fn apply_element_patch(node: &Element, patch: &Patch) -> Result<(), JsValue> {
|
||||
// let active_closures = HashMap::new();
|
||||
|
||||
match patch {
|
||||
Patch::AddAttributes(_node_idx, attributes) => {
|
||||
for (attrib_name, attrib_val) in attributes.iter() {
|
||||
node.set_attribute(attrib_name, attrib_val)?;
|
||||
}
|
||||
|
||||
// Ok(active_closures)
|
||||
Ok(())
|
||||
}
|
||||
Patch::RemoveAttributes(_node_idx, attributes) => {
|
||||
for attrib_name in attributes.iter() {
|
||||
node.remove_attribute(attrib_name)?;
|
||||
}
|
||||
|
||||
// Ok(active_closures)
|
||||
Ok(())
|
||||
}
|
||||
Patch::Replace(_node_idx, new_node) => {
|
||||
let created_node = create_dom_node(&new_node);
|
||||
|
||||
node.replace_with_with_node_1(&created_node.node)?;
|
||||
|
||||
Ok(())
|
||||
// Ok(created_node.closures)
|
||||
}
|
||||
Patch::TruncateChildren(_node_idx, num_children_remaining) => {
|
||||
let children = node.child_nodes();
|
||||
let mut child_count = children.length();
|
||||
|
||||
// We skip over any separators that we placed between two text nodes
|
||||
// -> `<!--ptns-->`
|
||||
// and trim all children that come after our new desired `num_children_remaining`
|
||||
let mut non_separator_children_found = 0;
|
||||
|
||||
for index in 0 as u32..child_count {
|
||||
let child = children
|
||||
.get(min(index, child_count - 1))
|
||||
.expect("Potential child to truncate");
|
||||
|
||||
// If this is a comment node then we know that it is a `<!--ptns-->`
|
||||
// text node separator that was created in virtual_node/mod.rs.
|
||||
if child.node_type() == Node::COMMENT_NODE {
|
||||
continue;
|
||||
}
|
||||
|
||||
non_separator_children_found += 1;
|
||||
|
||||
if non_separator_children_found <= *num_children_remaining as u32 {
|
||||
continue;
|
||||
}
|
||||
|
||||
node.remove_child(&child).expect("Truncated children");
|
||||
child_count -= 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// Ok(active_closures)
|
||||
}
|
||||
Patch::AppendChildren(_node_idx, new_nodes) => {
|
||||
let parent = &node;
|
||||
|
||||
let mut active_closures = HashMap::new();
|
||||
|
||||
for new_node in new_nodes {
|
||||
let created_node = create_dom_node(&new_node);
|
||||
// let created_node = new_node.create_dom_node();
|
||||
|
||||
parent.append_child(&created_node.node)?;
|
||||
|
||||
active_closures.extend(created_node.closures);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// Ok(active_closures)
|
||||
}
|
||||
Patch::ChangeText(_node_idx, _new_node) => {
|
||||
unreachable!("Elements should not receive ChangeText patches.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_text_patch(node: &Text, patch: &Patch) -> Result<(), JsValue> {
|
||||
match patch {
|
||||
Patch::ChangeText(_node_idx, new_node) => {
|
||||
node.set_node_value(Some(&new_node.text));
|
||||
}
|
||||
Patch::Replace(_node_idx, new_node) => {
|
||||
node.replace_with_with_node_1(&create_dom_node(&new_node).node)?;
|
||||
// node.replace_with_with_node_1(&new_node.create_dom_node().node)?;
|
||||
}
|
||||
other => unreachable!(
|
||||
"Text nodes should only receive ChangeText or Replace patches, not ",
|
||||
// other,
|
||||
// "Text nodes should only receive ChangeText or Replace patches, not {:?}.",
|
||||
// other,
|
||||
),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A node along with all of the closures that were created for that
|
||||
/// node's events and all of it's child node's events.
|
||||
pub struct CreatedNode<T> {
|
||||
/// A `Node` or `Element` that was created from a `VirtualNode`
|
||||
pub node: T,
|
||||
/// A map of a node's unique identifier along with all of the Closures for that node.
|
||||
///
|
||||
/// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
|
||||
/// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
|
||||
/// memory.
|
||||
pub closures: HashMap<u32, Vec<DynClosure>>,
|
||||
}
|
||||
|
||||
/// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
|
||||
/// any Closure regardless of the arguments.
|
||||
pub type DynClosure = Rc<dyn AsRef<JsValue>>;
|
||||
|
||||
impl<T> CreatedNode<T> {
|
||||
pub fn without_closures<N: Into<T>>(node: N) -> Self {
|
||||
CreatedNode {
|
||||
node: node.into(),
|
||||
closures: HashMap::with_capacity(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for CreatedNode<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.node
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CreatedNode<Element>> for CreatedNode<Node> {
|
||||
fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
|
||||
CreatedNode {
|
||||
node: other.node.into(),
|
||||
closures: other.closures,
|
||||
}
|
||||
}
|
||||
}
|
||||
fn create_dom_node(node: &VNode<'_>) -> CreatedNode<Node> {
|
||||
match node {
|
||||
VNode::Text(text_node) => CreatedNode::without_closures(create_text_node(text_node)),
|
||||
VNode::Element(element_node) => create_element_node(element_node).into(),
|
||||
// VNode::Element(element_node) => element_node.create_element_node().into(),
|
||||
VNode::Suspended => todo!(" not iimplemented yet"),
|
||||
VNode::Component(_) => todo!(" not iimplemented yet"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a DOM element by recursively creating DOM nodes for this element and it's
|
||||
/// children, it's children's children, etc.
|
||||
pub fn create_element_node(node: &dioxus_core::nodes::VElement) -> CreatedNode<Element> {
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
|
||||
// TODO: enable svg again
|
||||
// let element = if html_validation::is_svg_namespace(&node.tag_name) {
|
||||
// document
|
||||
// .create_element_ns(Some("http://www.w3.org/2000/svg"), &node.tag_name)
|
||||
// .unwrap()
|
||||
// } else {
|
||||
let element = document.create_element(&node.tag_name).unwrap();
|
||||
// };
|
||||
|
||||
let mut closures = HashMap::new();
|
||||
|
||||
node.attributes
|
||||
.iter()
|
||||
.map(|f| (f.name, f.value))
|
||||
.for_each(|(name, value)| {
|
||||
if name == "unsafe_inner_html" {
|
||||
element.set_inner_html(value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
element
|
||||
.set_attribute(name, value)
|
||||
.expect("Set element attribute in create element");
|
||||
});
|
||||
|
||||
// if node.events.0.len() > 0 {
|
||||
// let unique_id = create_unique_identifier();
|
||||
|
||||
// element
|
||||
// .set_attribute("data-vdom-id".into(), &unique_id.to_string())
|
||||
// .expect("Could not set attribute on element");
|
||||
|
||||
// closures.insert(unique_id, vec![]);
|
||||
|
||||
// node.events.0.iter().for_each(|(onevent, callback)| {
|
||||
// // onclick -> click
|
||||
// let event = &onevent[2..];
|
||||
|
||||
// let current_elem: &EventTarget = element.dyn_ref().unwrap();
|
||||
|
||||
// current_elem
|
||||
// .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref())
|
||||
// .unwrap();
|
||||
|
||||
// closures
|
||||
// .get_mut(&unique_id)
|
||||
// .unwrap()
|
||||
// .push(Rc::clone(callback));
|
||||
// });
|
||||
// }
|
||||
|
||||
let mut previous_node_was_text = false;
|
||||
|
||||
node.children.iter().for_each(|child| {
|
||||
// log::info!("Patching child");
|
||||
match child {
|
||||
VNode::Text(text_node) => {
|
||||
let current_node = element.as_ref() as &web_sys::Node;
|
||||
|
||||
// We ensure that the text siblings are patched by preventing the browser from merging
|
||||
// neighboring text nodes. Originally inspired by some of React's work from 2016.
|
||||
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
|
||||
// -> https://github.com/facebook/react/pull/5753
|
||||
//
|
||||
// `ptns` = Percy text node separator
|
||||
if previous_node_was_text {
|
||||
let separator = document.create_comment("ptns");
|
||||
current_node
|
||||
.append_child(separator.as_ref() as &web_sys::Node)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
current_node
|
||||
.append_child(&create_text_node(&text_node))
|
||||
.unwrap();
|
||||
|
||||
previous_node_was_text = true;
|
||||
}
|
||||
VNode::Element(element_node) => {
|
||||
previous_node_was_text = false;
|
||||
|
||||
let child = create_element_node(element_node);
|
||||
// let child = element_node.create_element_node();
|
||||
let child_elem: Element = child.node;
|
||||
|
||||
closures.extend(child.closures);
|
||||
|
||||
element.append_child(&child_elem).unwrap();
|
||||
}
|
||||
VNode::Suspended => {
|
||||
todo!("Not yet supported")
|
||||
}
|
||||
VNode::Component(_) => {
|
||||
todo!("Not yet supported")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: connect on mount to the event system somehow
|
||||
// if let Some(on_create_elem) = node.events.0.get("on_create_elem") {
|
||||
// let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref();
|
||||
// on_create_elem
|
||||
// .call1(&wasm_bindgen::JsValue::NULL, &element)
|
||||
// .unwrap();
|
||||
// }
|
||||
|
||||
CreatedNode {
|
||||
node: element,
|
||||
closures,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a `Text` element from a `VirtualNode`, typically right before adding it
|
||||
/// into the DOM.
|
||||
pub fn create_text_node(node: &VText) -> Text {
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
document.create_text_node(&node.text)
|
||||
}
|
||||
|
||||
// /// For any listeners in the tree, attach the sender closure.
|
||||
// /// When a event is triggered, we convert it into the synthetic event type and dump it back in the Virtual Dom's queu
|
||||
// fn attach_listeners(sender: &UnboundedSender<EventTrigger>, dom: &VirtualDom) {}
|
||||
// fn render_diffs() {}
|
||||
Reference in new issue
Block a user