Merge branch 'master' into events-2

This commit is contained in:
Evan Almloff committed 2024-01-04 19:02:00 -06:00
commit dccfba12d8
209 files changed
+6270 -2789

No files matched your search

+1 -1
View File
@@ -190,7 +190,7 @@ pub static BUILTIN_INTERNED_STRINGS: &[&str] = &[
"oncopy",
"oncuechange",
"oncut",
"ondblclick",
"ondoubleclick",
"ondrag",
"ondragend",
"ondragenter",
+35 -21
View File
@@ -4,7 +4,7 @@
//! - Passive event listeners
//! - no-op event listener patch for safari
//! - tests to ensure dyn_into works for various event types.
//! - Partial delegation?>
//! - Partial delegation?
use dioxus_core::{
BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode,
@@ -22,8 +22,8 @@ pub struct WebsysDom {
document: Document,
#[allow(dead_code)]
pub(crate) root: Element,
templates: FxHashMap<String, u32>,
max_template_id: u32,
templates: FxHashMap<String, u16>,
max_template_id: u16,
pub(crate) interpreter: Channel,
#[cfg(feature = "mounted")]
event_channel: mpsc::UnboundedSender<UiEvent>,
@@ -43,7 +43,16 @@ impl WebsysDom {
let document = load_document();
let root = match document.get_element_by_id(&cfg.rootname) {
Some(root) => root,
None => document.create_element("body").ok().unwrap(),
None => {
web_sys::console::error_1(
&format!(
"element '#{}' not found. mounting to the body.",
cfg.rootname
)
.into(),
);
document.create_element("body").ok().unwrap()
}
};
let interpreter = Channel::default();
@@ -88,7 +97,7 @@ impl WebsysDom {
}
}));
dioxus_interpreter_js::initilize(
dioxus_interpreter_js::initialize(
root.clone().unchecked_into(),
handler.as_ref().unchecked_ref(),
);
@@ -176,7 +185,7 @@ impl WebsysDom {
let mut to_mount = Vec::new();
for edit in &edits {
match edit {
AppendChildren { id, m } => i.append_children(id.0 as u32, *m as u32),
AppendChildren { id, m } => i.append_children(id.0 as u32, *m as u16),
AssignId { path, id } => {
i.assign_id(path.as_ptr() as u32, path.len() as u8, id.0 as u32)
}
@@ -187,15 +196,15 @@ impl WebsysDom {
}
LoadTemplate { name, index, id } => {
if let Some(tmpl_id) = self.templates.get(*name) {
i.load_template(*tmpl_id, *index as u32, id.0 as u32)
i.load_template(*tmpl_id, *index as u16, id.0 as u32)
}
}
ReplaceWith { id, m } => i.replace_with(id.0 as u32, *m as u32),
ReplaceWith { id, m } => i.replace_with(id.0 as u32, *m as u16),
ReplacePlaceholder { path, m } => {
i.replace_placeholder(path.as_ptr() as u32, path.len() as u8, *m as u32)
i.replace_placeholder(path.as_ptr() as u32, path.len() as u8, *m as u16)
}
InsertAfter { id, m } => i.insert_after(id.0 as u32, *m as u32),
InsertBefore { id, m } => i.insert_before(id.0 as u32, *m as u32),
InsertAfter { id, m } => i.insert_after(id.0 as u32, *m as u16),
InsertBefore { id, m } => i.insert_before(id.0 as u32, *m as u16),
SetAttribute {
name,
value,
@@ -250,16 +259,21 @@ impl WebsysDom {
#[cfg(feature = "mounted")]
for id in to_mount {
let node = dioxus_interpreter_js::get_node(id.0 as u32);
if let Some(element) = node.dyn_ref::<Element>() {
let data = PlatformEventData::new(Box::new(element.clone()));
let _ = self.event_channel.unbounded_send(UiEvent {
name: "mounted".to_string(),
bubbles: false,
element: id,
data,
});
}
self.send_mount_event(id);
}
}
pub(crate) fn send_mount_event(&self, id: ElementId) {
let node = get_node(id.0 as u32);
if let Some(element) = node.dyn_ref::<Element>() {
let data: MountedData = element.into();
let data = Rc::new(data);
let _ = self.event_channel.unbounded_send(UiEvent {
name: "mounted".to_string(),
bubbles: false,
element: id,
data,
});
}
}
}
+6 -9
View File
@@ -5,7 +5,6 @@ use futures_channel::mpsc::UnboundedReceiver;
use dioxus_core::Template;
pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
use std::convert::TryInto;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
use web_sys::{MessageEvent, WebSocket};
@@ -31,14 +30,12 @@ pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
// change the rsx when new data is received
let cl = Closure::wrap(Box::new(move |e: MessageEvent| {
if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() {
let text: Result<String, _> = text.try_into();
if let Ok(string) = text {
let val = serde_json::from_str::<serde_json::Value>(&string).unwrap();
// leak the value
let val: &'static serde_json::Value = Box::leak(Box::new(val));
let template: Template<'_> = Template::deserialize(val).unwrap();
tx.unbounded_send(template).unwrap();
}
let string: String = text.into();
let val = serde_json::from_str::<serde_json::Value>(&string).unwrap();
// leak the value
let val: &'static serde_json::Value = Box::leak(Box::new(val));
let template: Template<'_> = Template::deserialize(val).unwrap();
tx.unbounded_send(template).unwrap();
}
}) as Box<dyn FnMut(MessageEvent)>);
+9 -14
View File
@@ -220,14 +220,16 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
// todo: we need to split rebuild and initialize into two phases
// it's a waste to produce edits just to get the vdom loaded
let templates = dom.rebuild().templates;
websys_dom.load_templates(&templates);
{
let mutations = dom.rebuild();
web_sys::console::log_1(&format!("mutations: {:#?}", mutations).into());
let templates = mutations.templates;
websys_dom.load_templates(&templates);
websys_dom.interpreter.flush();
}
if let Err(err) = websys_dom.rehydrate(&dom) {
tracing::error!(
"Rehydration failed {:?}. Rebuild DOM into element from scratch",
&err
);
tracing::error!("Rehydration failed. {:?}", err);
tracing::error!("Rebuild DOM into element from scratch");
websys_dom.root.set_text_content(None);
let edits = dom.rebuild();
@@ -256,13 +258,6 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
pin_mut!(work);
#[cfg(all(feature = "hot_reload", debug_assertions))]
// futures_util::select! {
// _ = work => (None, None),
// new_template = hotreload_rx.next() => {
// (None, new_template)
// }
// evt = rx.next() =>
// }
match select(work, select(hotreload_rx.next(), rx.next())).await {
Either::Left((_, _)) => (None, None),
Either::Right((Either::Left((new_template, _)), _)) => (None, new_template),
+40 -190
View File
@@ -1,105 +1,62 @@
use crate::dom::WebsysDom;
use dioxus_core::{
AttributeValue, DynamicNode, ElementId, ScopeState, TemplateNode, VNode, VirtualDom,
};
use dioxus_html::event_bubbles;
use wasm_bindgen::JsCast;
use web_sys::{Comment, Node};
use dioxus_core::{DynamicNode, ElementId, ScopeState, TemplateNode, VNode, VirtualDom};
#[derive(Debug, Copy, Clone)]
#[derive(Debug)]
pub enum RehydrationError {
NodeTypeMismatch,
NodeNotFound,
VNodeNotInitialized,
}
use RehydrationError::*;
fn set_node(hydrated: &mut Vec<bool>, id: ElementId, node: Node) {
let idx = id.0;
if idx >= hydrated.len() {
hydrated.resize(idx + 1, false);
}
if !hydrated[idx] {
dioxus_interpreter_js::set_node(idx as u32, node);
hydrated[idx] = true;
}
}
use RehydrationError::*;
impl WebsysDom {
// we're streaming in patches, but the nodes already exist
// so we're just going to write the correct IDs to the node and load them in
pub fn rehydrate(&mut self, dom: &VirtualDom) -> Result<(), RehydrationError> {
let mut root = self
.root
.clone()
.dyn_into::<Node>()
.map_err(|_| NodeTypeMismatch)?
.first_child()
.ok_or(NodeNotFound);
let root_scope = dom.base_scope();
let mut hydrated = vec![true];
let mut last_node_was_static_text = false;
let mut ids = Vec::new();
let mut to_mount = Vec::new();
// Recursively rehydrate the dom from the VirtualDom
self.rehydrate_scope(
root_scope,
&mut root,
&mut hydrated,
dom,
&mut last_node_was_static_text,
)?;
self.rehydrate_scope(root_scope, dom, &mut ids, &mut to_mount)?;
dioxus_interpreter_js::hydrate(ids);
for id in to_mount {
self.send_mount_event(id);
}
self.interpreter.flush();
Ok(())
}
fn rehydrate_scope(
&mut self,
scope: &ScopeState,
current_child: &mut Result<Node, RehydrationError>,
hydrated: &mut Vec<bool>,
dom: &VirtualDom,
last_node_was_static_text: &mut bool,
ids: &mut Vec<u32>,
to_mount: &mut Vec<ElementId>,
) -> Result<(), RehydrationError> {
let vnode = match scope.root_node() {
dioxus_core::RenderReturn::Ready(ready) => ready,
_ => return Err(VNodeNotInitialized),
};
self.rehydrate_vnode(
current_child,
hydrated,
dom,
vnode,
last_node_was_static_text,
)
self.rehydrate_vnode(dom, vnode, ids, to_mount)
}
fn rehydrate_vnode(
&mut self,
current_child: &mut Result<Node, RehydrationError>,
hydrated: &mut Vec<bool>,
dom: &VirtualDom,
vnode: &VNode,
last_node_was_static_text: &mut bool,
ids: &mut Vec<u32>,
to_mount: &mut Vec<ElementId>,
) -> Result<(), RehydrationError> {
for (i, root) in vnode.template.get().roots.iter().enumerate() {
// make sure we set the root node ids even if the node is not dynamic
set_node(
hydrated,
*vnode.root_ids.borrow().get(i).ok_or(VNodeNotInitialized)?,
current_child.clone()?,
);
self.rehydrate_template_node(
current_child,
hydrated,
dom,
vnode,
root,
last_node_was_static_text,
ids,
to_mount,
Some(*vnode.root_ids.borrow().get(i).ok_or(VNodeNotInitialized)?),
)?;
}
Ok(())
@@ -107,182 +64,75 @@ impl WebsysDom {
fn rehydrate_template_node(
&mut self,
current_child: &mut Result<Node, RehydrationError>,
hydrated: &mut Vec<bool>,
dom: &VirtualDom,
vnode: &VNode,
node: &TemplateNode,
last_node_was_static_text: &mut bool,
ids: &mut Vec<u32>,
to_mount: &mut Vec<ElementId>,
root_id: Option<ElementId>,
) -> Result<(), RehydrationError> {
tracing::trace!("rehydrate template node: {:?}", node);
if let Ok(current_child) = current_child {
if tracing::event_enabled!(tracing::Level::TRACE) {
web_sys::console::log_1(&current_child.clone().into());
}
}
match node {
TemplateNode::Element {
children, attrs, ..
} => {
let mut mounted_id = None;
let mut mounted_id = root_id;
for attr in *attrs {
if let dioxus_core::TemplateAttribute::Dynamic { id } = attr {
let attribute = &vnode.dynamic_attrs[*id];
let value = &attribute.value;
let id = attribute.mounted_element();
mounted_id = Some(id);
let name = attribute.name;
if let AttributeValue::Listener(_) = value {
let event_name = &name[2..];
self.interpreter.new_event_listener(
event_name,
id.0 as u32,
event_bubbles(event_name) as u8,
);
if let dioxus_core::AttributeValue::Listener(_) = attribute.value {
if attribute.name == "onmounted" {
to_mount.push(id);
}
}
}
}
if let Some(id) = mounted_id {
set_node(hydrated, id, current_child.clone()?);
ids.push(id.0 as u32);
}
if !children.is_empty() {
let mut children_current_child = current_child
.as_mut()
.map_err(|e| *e)?
.first_child()
.ok_or(NodeNotFound)?
.dyn_into::<Node>()
.map_err(|_| NodeTypeMismatch);
for child in *children {
self.rehydrate_template_node(
&mut children_current_child,
hydrated,
dom,
vnode,
child,
last_node_was_static_text,
)?;
self.rehydrate_template_node(dom, vnode, child, ids, to_mount, None)?;
}
}
*current_child = current_child
.as_mut()
.map_err(|e| *e)?
.next_sibling()
.ok_or(NodeNotFound);
*last_node_was_static_text = false;
}
TemplateNode::Text { .. } => {
// if the last node was static text, it got merged with this one
if !*last_node_was_static_text {
*current_child = current_child
.as_mut()
.map_err(|e| *e)?
.next_sibling()
.ok_or(NodeNotFound);
}
*last_node_was_static_text = true;
}
TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
self.rehydrate_dynamic_node(
current_child,
hydrated,
dom,
&vnode.dynamic_nodes[*id],
last_node_was_static_text,
)?;
self.rehydrate_dynamic_node(dom, &vnode.dynamic_nodes[*id], ids, to_mount)?;
}
_ => {}
}
Ok(())
}
fn rehydrate_dynamic_node(
&mut self,
current_child: &mut Result<Node, RehydrationError>,
hydrated: &mut Vec<bool>,
dom: &VirtualDom,
dynamic: &DynamicNode,
last_node_was_static_text: &mut bool,
ids: &mut Vec<u32>,
to_mount: &mut Vec<ElementId>,
) -> Result<(), RehydrationError> {
tracing::trace!("rehydrate dynamic node: {:?}", dynamic);
if let Ok(current_child) = current_child {
if tracing::event_enabled!(tracing::Level::TRACE) {
web_sys::console::log_1(&current_child.clone().into());
}
}
match dynamic {
dioxus_core::DynamicNode::Text(text) => {
let id = text.mounted_element();
// skip comment separator before node
if cfg!(debug_assertions) {
assert!(current_child
.as_mut()
.map_err(|e| *e)?
.has_type::<Comment>());
}
*current_child = current_child
.as_mut()
.map_err(|e| *e)?
.next_sibling()
.ok_or(NodeNotFound);
set_node(
hydrated,
id.ok_or(VNodeNotInitialized)?,
current_child.clone()?,
);
*current_child = current_child
.as_mut()
.map_err(|e| *e)?
.next_sibling()
.ok_or(NodeNotFound);
// skip comment separator after node
if cfg!(debug_assertions) {
assert!(current_child
.as_mut()
.map_err(|e| *e)?
.has_type::<Comment>());
}
*current_child = current_child
.as_mut()
.map_err(|e| *e)?
.next_sibling()
.ok_or(NodeNotFound);
*last_node_was_static_text = false;
ids.push(text.mounted_element().ok_or(VNodeNotInitialized)?.0 as u32);
}
dioxus_core::DynamicNode::Placeholder(placeholder) => {
set_node(
hydrated,
placeholder.mounted_element().ok_or(VNodeNotInitialized)?,
current_child.clone()?,
);
*current_child = current_child
.as_mut()
.map_err(|e| *e)?
.next_sibling()
.ok_or(NodeNotFound);
*last_node_was_static_text = false;
ids.push(placeholder.mounted_element().ok_or(VNodeNotInitialized)?.0 as u32);
}
dioxus_core::DynamicNode::Component(comp) => {
let scope = comp.mounted_scope().ok_or(VNodeNotInitialized)?;
self.rehydrate_scope(
dom.get_scope(scope).unwrap(),
current_child,
hydrated,
dom.get_scope(scope).ok_or(VNodeNotInitialized)?,
dom,
last_node_was_static_text,
ids,
to_mount,
)?;
}
dioxus_core::DynamicNode::Fragment(fragment) => {
for vnode in *fragment {
self.rehydrate_vnode(
current_child,
hydrated,
dom,
vnode,
last_node_was_static_text,
)?;
self.rehydrate_vnode(dom, vnode, ids, to_mount)?;
}
}
}