polish: tests and documentation
This commit is contained in:
1 parent
0896f34493
commit
da8159190b
27 files changed
+467
-569
No files matched your search
Vendored
+1
@@ -66,3 +66,4 @@ constified
|
||||
SegVec
|
||||
contentful
|
||||
Jank
|
||||
noderef
|
||||
@@ -109,6 +109,8 @@ If you know React, then you already know Dioxus.
|
||||
- Sierpinski's triangle (web SPA)
|
||||
- Doxie Documentation Library (Web SPA with Hydration)
|
||||
|
||||
See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem.
|
||||
|
||||
<!--
|
||||
currently commented out until we have more content on the website
|
||||
## Explore
|
||||
|
||||
@@ -14,6 +14,7 @@ pub(crate) struct BumpFrame {
|
||||
pub bump: Bump,
|
||||
pub(crate) head_node: VNode<'static>,
|
||||
|
||||
#[cfg(test)]
|
||||
// used internally for debugging
|
||||
_name: &'static str,
|
||||
}
|
||||
@@ -23,11 +24,15 @@ impl ActiveFrame {
|
||||
let frame_a = BumpFrame {
|
||||
bump: Bump::new(),
|
||||
head_node: NodeFactory::unstable_place_holder(),
|
||||
|
||||
#[cfg(test)]
|
||||
_name: "wip",
|
||||
};
|
||||
let frame_b = BumpFrame {
|
||||
bump: Bump::new(),
|
||||
head_node: NodeFactory::unstable_place_holder(),
|
||||
|
||||
#[cfg(test)]
|
||||
_name: "fin",
|
||||
};
|
||||
Self {
|
||||
|
||||
@@ -64,7 +64,6 @@ impl<'a> Iterator for RealChildIterator<'a> {
|
||||
.scopes
|
||||
.get_scope(sc.associated_scope.get().unwrap())
|
||||
.unwrap();
|
||||
// let scope = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap();
|
||||
|
||||
// Simply swap the current node on the stack with the root of the component
|
||||
*node = scope.frames.fin_head();
|
||||
|
||||
@@ -7,6 +7,32 @@
|
||||
|
||||
use crate::innerlude::{Context, DomTree, LazyNodes, FC};
|
||||
|
||||
/// Create inline fragments using Component syntax.
|
||||
///
|
||||
/// Fragments capture a series of children without rendering extra nodes.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// rsx!{
|
||||
/// Fragment { key: "abc" }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Details
|
||||
///
|
||||
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
||||
/// Try to avoid nesting fragments if you can. There is no protection against infinitely nested fragments.
|
||||
///
|
||||
/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
|
||||
///
|
||||
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
||||
///
|
||||
#[allow(non_upper_case_globals, non_snake_case)]
|
||||
pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
|
||||
cx.render(LazyNodes::new(|f| f.fragment_from_iter(cx.children())))
|
||||
}
|
||||
|
||||
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
|
||||
/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
|
||||
/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
|
||||
@@ -44,7 +70,7 @@ pub trait Properties: Sized {
|
||||
const IS_STATIC: bool;
|
||||
fn builder() -> Self::Builder;
|
||||
|
||||
/// Memoization can only happen if the props are 'static
|
||||
/// Memoization can only happen if the props are valid for the 'static lifetime
|
||||
/// The user must know if their props are static, but if they make a mistake, UB happens
|
||||
/// Therefore it's unsafe to memeoize.
|
||||
unsafe fn memoize(&self, other: &Self) -> bool;
|
||||
@@ -75,24 +101,3 @@ impl EmptyBuilder {
|
||||
pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
|
||||
T::builder()
|
||||
}
|
||||
|
||||
/// Create inline fragments
|
||||
///
|
||||
/// Fragments capture a series of children without rendering extra nodes.
|
||||
///
|
||||
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
||||
/// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash.
|
||||
///
|
||||
/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
|
||||
///
|
||||
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
||||
///
|
||||
/// ```rust
|
||||
/// rsx!{
|
||||
/// Fragment { key: "abc" }
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(non_upper_case_globals, non_snake_case)]
|
||||
pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
|
||||
cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children())))
|
||||
}
|
||||
+59
-117
@@ -124,6 +124,7 @@ impl<'a> SavedDiffWork<'a> {
|
||||
pub unsafe fn extend(self: SavedDiffWork<'a>) -> SavedDiffWork<'static> {
|
||||
std::mem::transmute(self)
|
||||
}
|
||||
|
||||
pub unsafe fn promote<'b>(self, vdom: &'b mut ResourcePool) -> DiffMachine<'b> {
|
||||
let extended: SavedDiffWork<'b> = std::mem::transmute(self);
|
||||
DiffMachine {
|
||||
@@ -136,10 +137,10 @@ impl<'a> SavedDiffWork<'a> {
|
||||
}
|
||||
|
||||
impl<'bump> DiffMachine<'bump> {
|
||||
pub(crate) fn new(edits: Mutations<'bump>, shared: &'bump ResourcePool) -> Self {
|
||||
pub(crate) fn new(mutations: Mutations<'bump>, shared: &'bump ResourcePool) -> Self {
|
||||
Self {
|
||||
mutations,
|
||||
stack: DiffStack::new(),
|
||||
mutations: edits,
|
||||
vdom: shared,
|
||||
seen_scopes: FxHashSet::default(),
|
||||
}
|
||||
@@ -156,7 +157,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
pub fn diff_scope(&mut self, id: ScopeId) {
|
||||
if let Some(component) = self.vdom.get_scope_mut(id) {
|
||||
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
||||
self.stack.push(DiffInstruction::DiffNode { new, old });
|
||||
self.stack.push(DiffInstruction::Diff { new, old });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,24 +168,14 @@ impl<'bump> DiffMachine<'bump> {
|
||||
/// We do depth-first to maintain high cache locality (nodes were originally generated recursively).
|
||||
///
|
||||
/// Returns a `bool` indicating that the work completed properly.
|
||||
pub fn work(&mut self, deadline_expired: &mut impl FnMut() -> bool) -> bool {
|
||||
pub fn work(&mut self, mut deadline_expired: impl FnMut() -> bool) -> bool {
|
||||
while let Some(instruction) = self.stack.pop() {
|
||||
// defer to individual functions so the compiler produces better code
|
||||
// large functions tend to be difficult for the compiler to work with
|
||||
match instruction {
|
||||
DiffInstruction::PopScope => {
|
||||
self.stack.pop_scope();
|
||||
}
|
||||
|
||||
DiffInstruction::DiffNode { old, new, .. } => self.diff_node(old, new),
|
||||
|
||||
DiffInstruction::DiffChildren { old, new } => self.diff_children(old, new),
|
||||
|
||||
DiffInstruction::Diff { old, new } => self.diff_node(old, new),
|
||||
DiffInstruction::Create { node } => self.create_node(node),
|
||||
|
||||
DiffInstruction::Mount { and } => self.mount(and),
|
||||
|
||||
DiffInstruction::PrepareMoveNode { node } => self.prepare_move_node(node),
|
||||
DiffInstruction::PrepareMove { node } => self.prepare_move_node(node),
|
||||
DiffInstruction::PopScope => self.stack.pop_off_scope(),
|
||||
};
|
||||
|
||||
if deadline_expired() {
|
||||
@@ -217,15 +208,21 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
|
||||
MountType::Replace { old } => {
|
||||
let mut iter = RealChildIterator::new(old, self.vdom);
|
||||
let first = iter.next().unwrap();
|
||||
self.mutations
|
||||
.replace_with(first.mounted_id(), nodes_created as u32);
|
||||
self.remove_nodes(iter);
|
||||
if let Some(old_id) = old.try_mounted_id() {
|
||||
self.mutations.replace_with(old_id, nodes_created as u32);
|
||||
} else {
|
||||
let mut iter = RealChildIterator::new(old, self.vdom);
|
||||
let first = iter.next().unwrap();
|
||||
self.mutations
|
||||
.replace_with(first.mounted_id(), nodes_created as u32);
|
||||
self.remove_nodes(iter);
|
||||
}
|
||||
}
|
||||
|
||||
MountType::ReplaceByElementId { el: old } => {
|
||||
self.mutations.replace_with(old, nodes_created as u32);
|
||||
MountType::ReplaceByElementId { el } => {
|
||||
if let Some(old) = el {
|
||||
self.mutations.replace_with(old, nodes_created as u32);
|
||||
}
|
||||
}
|
||||
|
||||
MountType::InsertAfter { other_node } => {
|
||||
@@ -326,6 +323,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
let parent_idx = self.stack.current_scope().unwrap();
|
||||
|
||||
let shared = self.vdom.channel.clone();
|
||||
|
||||
// Insert a new scope into our component list
|
||||
let parent_scope = self.vdom.get_scope(parent_idx).unwrap();
|
||||
let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
|
||||
@@ -376,7 +374,9 @@ impl<'bump> DiffMachine<'bump> {
|
||||
match (old_node, new_node) {
|
||||
// Check the most common cases first
|
||||
(Text(old), Text(new)) => self.diff_text_nodes(old, new),
|
||||
(Component(old), Component(new)) => self.diff_component_nodes(old, new),
|
||||
(Component(old), Component(new)) => {
|
||||
self.diff_component_nodes(old_node, new_node, old, new)
|
||||
}
|
||||
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
|
||||
(Anchor(old), Anchor(new)) => new.dom_id.set(old.dom_id.get()),
|
||||
(Suspended(old), Suspended(new)) => new.dom_id.set(old.dom_id.get()),
|
||||
@@ -393,19 +393,19 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
|
||||
fn diff_text_nodes(&mut self, old: &'bump VText<'bump>, new: &'bump VText<'bump>) {
|
||||
let root = old.dom_id.get().unwrap();
|
||||
if let Some(root) = old.dom_id.get() {
|
||||
if old.text != new.text {
|
||||
self.mutations.push_root(root);
|
||||
self.mutations.set_text(new.text);
|
||||
self.mutations.pop();
|
||||
}
|
||||
|
||||
if old.text != new.text {
|
||||
self.mutations.push_root(root);
|
||||
self.mutations.set_text(new.text);
|
||||
self.mutations.pop();
|
||||
new.dom_id.set(Some(root));
|
||||
}
|
||||
|
||||
new.dom_id.set(Some(root));
|
||||
}
|
||||
|
||||
fn diff_element_nodes(&mut self, old: &'bump VElement<'bump>, new: &'bump VElement<'bump>) {
|
||||
let root = old.dom_id.get().unwrap();
|
||||
let root = old.dom_id.get();
|
||||
|
||||
// If the element type is completely different, the element needs to be re-rendered completely
|
||||
// This is an optimization React makes due to how users structure their code
|
||||
@@ -417,21 +417,23 @@ impl<'bump> DiffMachine<'bump> {
|
||||
self.stack.push_nodes_created(0);
|
||||
self.stack.push(DiffInstruction::Mount {
|
||||
and: MountType::ReplaceByElementId {
|
||||
el: old.dom_id.get().unwrap(),
|
||||
el: old.dom_id.get(),
|
||||
},
|
||||
});
|
||||
self.create_element_node(new);
|
||||
return;
|
||||
}
|
||||
|
||||
new.dom_id.set(Some(root));
|
||||
new.dom_id.set(root);
|
||||
|
||||
// Don't push the root if we don't have to
|
||||
let mut has_comitted = false;
|
||||
let mut please_commit = |edits: &mut Vec<DomEdit>| {
|
||||
if !has_comitted {
|
||||
has_comitted = true;
|
||||
edits.push(PushRoot { id: root.as_u64() });
|
||||
if let Some(root) = root {
|
||||
edits.push(PushRoot { id: root.as_u64() });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -449,7 +451,6 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: provide some sort of report on how "good" the diffing was
|
||||
please_commit(&mut self.mutations.edits);
|
||||
for attribute in old.attributes {
|
||||
self.mutations.remove_attribute(attribute);
|
||||
@@ -467,7 +468,6 @@ impl<'bump> DiffMachine<'bump> {
|
||||
// We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
|
||||
//
|
||||
// TODO: take a more efficient path than this
|
||||
|
||||
if let Some(cur_scope_id) = self.stack.current_scope() {
|
||||
let scope = self.vdom.get_scope(cur_scope_id).unwrap();
|
||||
|
||||
@@ -487,22 +487,21 @@ impl<'bump> DiffMachine<'bump> {
|
||||
self.mutations.remove_event_listener(listener.event);
|
||||
}
|
||||
for listener in new.listeners {
|
||||
listener.mounted_node.set(Some(root));
|
||||
listener.mounted_node.set(root);
|
||||
self.mutations.new_event_listener(listener, cur_scope_id);
|
||||
self.attach_listener_to_scope(listener, scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if has_comitted {
|
||||
self.mutations.pop();
|
||||
}
|
||||
|
||||
self.diff_children(old.children, new.children);
|
||||
}
|
||||
|
||||
fn diff_component_nodes(
|
||||
&mut self,
|
||||
old_node: &'bump VNode<'bump>,
|
||||
new_node: &'bump VNode<'bump>,
|
||||
|
||||
old: &'bump VComponent<'bump>,
|
||||
new: &'bump VComponent<'bump>,
|
||||
) {
|
||||
@@ -526,7 +525,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
|
||||
match compare(new) {
|
||||
true => {
|
||||
// the props are the same...
|
||||
// the props are the same... do nothing
|
||||
}
|
||||
false => {
|
||||
// the props are different...
|
||||
@@ -540,25 +539,8 @@ impl<'bump> DiffMachine<'bump> {
|
||||
|
||||
self.seen_scopes.insert(scope_addr);
|
||||
} else {
|
||||
todo!();
|
||||
|
||||
// let mut old_iter = RealChildIterator::new(old_node, &self.vdom);
|
||||
// let first = old_iter
|
||||
// .next()
|
||||
// .expect("Components should generate a placeholder root");
|
||||
|
||||
// // remove any leftovers
|
||||
// for to_remove in old_iter {
|
||||
// self.mutations.push_root(to_remove.direct_id());
|
||||
// self.mutations.remove();
|
||||
// }
|
||||
|
||||
// // seems like we could combine this into a single instruction....
|
||||
// self.replace_node_with_node(first.direct_id(), old_node, new_node);
|
||||
|
||||
// // Wipe the old one and plant the new one
|
||||
// let old_scope = old.ass_scope.get().unwrap();
|
||||
// self.destroy_scopes(old_scope);
|
||||
self.stack
|
||||
.create_node(new_node, MountType::Replace { old: old_node });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,7 +590,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
old_anchor.dom_id.set(new_anchor.dom_id.get());
|
||||
}
|
||||
([VNode::Anchor(anchor)], _) => {
|
||||
let el = anchor.dom_id.get().unwrap();
|
||||
let el = anchor.dom_id.get();
|
||||
self.stack
|
||||
.create_children(new, MountType::ReplaceByElementId { el });
|
||||
}
|
||||
@@ -652,7 +634,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
debug_assert!(!old.is_empty());
|
||||
|
||||
for (new, old) in new.iter().zip(old.iter()).rev() {
|
||||
self.stack.push(DiffInstruction::DiffNode { new, old });
|
||||
self.stack.push(DiffInstruction::Diff { new, old });
|
||||
}
|
||||
|
||||
if old.len() > new.len() {
|
||||
@@ -748,7 +730,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
if old.key() != new.key() {
|
||||
break;
|
||||
}
|
||||
self.stack.push(DiffInstruction::DiffNode { old, new });
|
||||
self.stack.push(DiffInstruction::Diff { old, new });
|
||||
left_offset += 1;
|
||||
}
|
||||
|
||||
@@ -887,8 +869,8 @@ impl<'bump> DiffMachine<'bump> {
|
||||
stack.create_node(new_node, MountType::Absorb);
|
||||
} else {
|
||||
// this funciton should never take LIS indicies
|
||||
stack.push(DiffInstruction::PrepareMoveNode { node: new_node });
|
||||
stack.push(DiffInstruction::DiffNode {
|
||||
stack.push(DiffInstruction::PrepareMove { node: new_node });
|
||||
stack.push(DiffInstruction::Diff {
|
||||
new: new_node,
|
||||
old: &old[old_index],
|
||||
});
|
||||
@@ -943,7 +925,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
|
||||
for idx in lis_sequence.iter().rev() {
|
||||
self.stack.push(DiffInstruction::DiffNode {
|
||||
self.stack.push(DiffInstruction::Diff {
|
||||
new: &new[*idx],
|
||||
old: &old[new_index_to_old_index[*idx]],
|
||||
});
|
||||
@@ -998,6 +980,14 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_and_create_one_with_many(
|
||||
&mut self,
|
||||
old: &'bump VNode<'bump>,
|
||||
new: &'bump [VNode<'bump>],
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
fn replace_and_create_many_with_one(
|
||||
&mut self,
|
||||
old: &'bump [VNode<'bump>],
|
||||
@@ -1070,60 +1060,12 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_garbage(&mut self, node: &'bump VNode<'bump>) {
|
||||
match self
|
||||
.stack
|
||||
.current_scope()
|
||||
.and_then(|id| self.vdom.get_scope(id))
|
||||
{
|
||||
Some(scope) => {
|
||||
let garbage: &'bump VNode<'static> = unsafe { std::mem::transmute(node) };
|
||||
scope.pending_garbage.borrow_mut().push(garbage);
|
||||
}
|
||||
None => {
|
||||
log::info!("No scope to collect garbage into")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a listener closure to a scope during diff.
|
||||
fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &Scope) {
|
||||
let mut queue = scope.listeners.borrow_mut();
|
||||
let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
|
||||
queue.push(long_listener as *const _)
|
||||
}
|
||||
|
||||
/// Destroy a scope and all of its descendents.
|
||||
///
|
||||
/// Calling this will run the destuctors on all hooks in the tree.
|
||||
/// It will also add the destroyed nodes to the `seen_nodes` cache to prevent them from being renderered.
|
||||
fn destroy_scopes(&mut self, old_scope: ScopeId) {
|
||||
let mut nodes_to_delete = vec![old_scope];
|
||||
let mut scopes_to_explore = vec![old_scope];
|
||||
|
||||
// explore the scope tree breadth first
|
||||
while let Some(scope_id) = scopes_to_explore.pop() {
|
||||
// If we're planning on deleting this node, then we don't need to both rendering it
|
||||
self.seen_scopes.insert(scope_id);
|
||||
let scope = self.vdom.get_scope(scope_id).unwrap();
|
||||
for child in scope.descendents.borrow().iter() {
|
||||
// Add this node to be explored
|
||||
scopes_to_explore.push(child.clone());
|
||||
|
||||
// Also add it for deletion
|
||||
nodes_to_delete.push(child.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all scopes that we found as part of this subtree
|
||||
for node in nodes_to_delete {
|
||||
log::debug!("Removing scope {:#?}", node);
|
||||
let _scope = self.vdom.try_remove(node).unwrap();
|
||||
// do anything we need to do to delete the scope
|
||||
// I think we need to run the destructors on the hooks
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_strs(a: &str, b: &str) -> bool {
|
||||
|
||||
@@ -3,23 +3,18 @@ use smallvec::{smallvec, SmallVec};
|
||||
|
||||
/// The stack instructions we use to diff and create new nodes.
|
||||
#[derive(Debug)]
|
||||
pub enum DiffInstruction<'a> {
|
||||
DiffNode {
|
||||
pub(crate) enum DiffInstruction<'a> {
|
||||
Diff {
|
||||
old: &'a VNode<'a>,
|
||||
new: &'a VNode<'a>,
|
||||
},
|
||||
|
||||
DiffChildren {
|
||||
old: &'a [VNode<'a>],
|
||||
new: &'a [VNode<'a>],
|
||||
},
|
||||
|
||||
Create {
|
||||
node: &'a VNode<'a>,
|
||||
},
|
||||
|
||||
/// pushes the node elements onto the stack for use in mount
|
||||
PrepareMoveNode {
|
||||
PrepareMove {
|
||||
node: &'a VNode<'a>,
|
||||
},
|
||||
|
||||
@@ -35,7 +30,7 @@ pub enum MountType<'a> {
|
||||
Absorb,
|
||||
Append,
|
||||
Replace { old: &'a VNode<'a> },
|
||||
ReplaceByElementId { el: ElementId },
|
||||
ReplaceByElementId { el: Option<ElementId> },
|
||||
InsertAfter { other_node: &'a VNode<'a> },
|
||||
InsertBefore { other_node: &'a VNode<'a> },
|
||||
}
|
||||
@@ -63,6 +58,10 @@ impl<'bump> DiffStack<'bump> {
|
||||
self.instructions.pop()
|
||||
}
|
||||
|
||||
pub fn pop_off_scope(&mut self) {
|
||||
self.scope_stack.pop();
|
||||
}
|
||||
|
||||
pub fn pop_scope(&mut self) -> Option<ScopeId> {
|
||||
self.scope_stack.pop()
|
||||
}
|
||||
|
||||
@@ -125,8 +125,13 @@ pub mod on {
|
||||
|
||||
let callback: BumpBox<dyn FnMut(SyntheticEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
|
||||
|
||||
|
||||
// ie oncopy
|
||||
let event_name = stringify!($name);
|
||||
|
||||
// ie copy
|
||||
let shortname: &'static str = &event_name[2..];
|
||||
|
||||
Listener {
|
||||
event: shortname,
|
||||
mounted_node: Cell::new(None),
|
||||
|
||||
@@ -3,45 +3,28 @@ use std::{
|
||||
cell::{Cell, RefCell, UnsafeCell},
|
||||
};
|
||||
|
||||
/// An abstraction over internally stored data using a hook-based memory layout.
|
||||
///
|
||||
/// Hooks are allocated using Boxes and then our stored references are given out.
|
||||
///
|
||||
/// It's unsafe to "reset" the hooklist, but it is safe to add hooks into it.
|
||||
///
|
||||
/// Todo: this could use its very own bump arena, but that might be a tad overkill
|
||||
#[derive(Default)]
|
||||
pub(crate) struct HookList {
|
||||
vals: RefCell<Vec<InnerHook<Box<dyn Any>>>>,
|
||||
vals: RefCell<Vec<UnsafeCell<Box<dyn Any>>>>,
|
||||
idx: Cell<usize>,
|
||||
}
|
||||
|
||||
impl Default for HookList {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
vals: Default::default(),
|
||||
idx: Cell::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InnerHook<T> {
|
||||
cell: UnsafeCell<T>,
|
||||
}
|
||||
|
||||
impl<T> InnerHook<T> {
|
||||
fn new(new: T) -> Self {
|
||||
Self {
|
||||
cell: UnsafeCell::new(new),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HookList {
|
||||
pub(crate) fn next<T: 'static>(&self) -> Option<&mut T> {
|
||||
self.vals.borrow().get(self.idx.get()).and_then(|inn| {
|
||||
self.idx.set(self.idx.get() + 1);
|
||||
let raw_box = unsafe { &mut *inn.cell.get() };
|
||||
let raw_box = unsafe { &mut *inn.get() };
|
||||
raw_box.downcast_mut::<T>()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn push<T: 'static>(&self, new: T) {
|
||||
self.vals.borrow_mut().push(InnerHook::new(Box::new(new)))
|
||||
}
|
||||
|
||||
/// This resets the internal iterator count
|
||||
/// It's okay that we've given out each hook, but now we have the opportunity to give it out again
|
||||
/// Therefore, resetting is cosudered unsafe
|
||||
@@ -52,17 +35,18 @@ impl HookList {
|
||||
self.idx.set(0);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn push<T: 'static>(&self, new: T) {
|
||||
self.vals.borrow_mut().push(UnsafeCell::new(Box::new(new)))
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.vals.borrow().len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn cur_idx(&self) -> usize {
|
||||
self.idx.get()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn at_end(&self) -> bool {
|
||||
self.cur_idx() >= self.len()
|
||||
}
|
||||
|
||||
@@ -56,9 +56,9 @@ pub(crate) mod innerlude {
|
||||
}
|
||||
|
||||
pub use crate::innerlude::{
|
||||
format_args_f, html, rsx, Context, DiffInstruction, DioxusElement, DomEdit, DomTree, ElementId,
|
||||
EventPriority, LazyNodes, MountType, Mutations, NodeFactory, Properties, ScopeId,
|
||||
SuspendedContext, SyntheticEvent, TestDom, UserEvent, VNode, VirtualDom, FC,
|
||||
format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority,
|
||||
LazyNodes, MountType, Mutations, NodeFactory, Properties, ScopeId, SuspendedContext,
|
||||
SyntheticEvent, TestDom, UserEvent, VNode, VirtualDom, FC,
|
||||
};
|
||||
|
||||
pub mod prelude {
|
||||
|
||||
@@ -26,6 +26,7 @@ pub enum VNode<'src> {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// let node = cx.render(rsx!{ "hello" }).unwrap();
|
||||
///
|
||||
/// if let VNode::Text(vtext) = node {
|
||||
@@ -248,7 +249,7 @@ pub struct Listener<'bump> {
|
||||
|
||||
/// The type of event to listen for.
|
||||
///
|
||||
/// IE "onclick" - whatever the renderer needs to attach the listener by name.
|
||||
/// IE "click" - whatever the renderer needs to attach the listener by name.
|
||||
pub event: &'static str,
|
||||
|
||||
pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(SyntheticEvent) + 'bump>>>,
|
||||
@@ -283,8 +284,7 @@ pub struct VComponent<'src> {
|
||||
pub struct VSuspended<'a> {
|
||||
pub task_id: u64,
|
||||
pub dom_id: Cell<Option<ElementId>>,
|
||||
pub(crate) callback:
|
||||
RefCell<Option<BumpBox<'a, dyn FnMut(SuspendedContext<'a>) -> DomTree<'a>>>>,
|
||||
pub callback: RefCell<Option<BumpBox<'a, dyn FnMut(SuspendedContext<'a>) -> DomTree<'a>>>>,
|
||||
}
|
||||
|
||||
/// This struct provides an ergonomic API to quickly build VNodes.
|
||||
|
||||
@@ -14,7 +14,7 @@ Some essential reading:
|
||||
|
||||
Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI
|
||||
snappy and "jank free" even under heavy work loads. Dioxus already has the "speed" part figured out - but there's no
|
||||
point if being "fast" if you can't also be "responsive."
|
||||
point in being "fast" if you can't also be "responsive."
|
||||
|
||||
As such, Dioxus can manually decide on what work is most important at any given moment in time. With a properly tuned
|
||||
priority system, Dioxus can ensure that user interaction is prioritized and committed as soon as possible (sub 100ms).
|
||||
@@ -87,7 +87,7 @@ use std::{
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventChannel {
|
||||
pub(crate) struct EventChannel {
|
||||
pub task_counter: Rc<Cell<u64>>,
|
||||
pub sender: UnboundedSender<SchedulerMsg>,
|
||||
pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
|
||||
@@ -96,8 +96,13 @@ pub struct EventChannel {
|
||||
}
|
||||
|
||||
pub enum SchedulerMsg {
|
||||
Immediate(ScopeId),
|
||||
// events from the host
|
||||
UiEvent(UserEvent),
|
||||
|
||||
// setstate
|
||||
Immediate(ScopeId),
|
||||
|
||||
// tasks
|
||||
SubmitTask(FiberTask, u64),
|
||||
ToggleTask(u64),
|
||||
PauseTask(u64),
|
||||
@@ -109,7 +114,7 @@ pub enum SchedulerMsg {
|
||||
///
|
||||
/// Each scope has the ability to lightly interact with the scheduler (IE, schedule an update) but ultimately the scheduler calls the components.
|
||||
///
|
||||
/// In Dioxus, the scheduler provides 3 priority levels - each with their own "DiffMachine". The DiffMachine state can be saved if the deadline runs
|
||||
/// In Dioxus, the scheduler provides 4 priority levels - each with their own "DiffMachine". The DiffMachine state can be saved if the deadline runs
|
||||
/// out.
|
||||
///
|
||||
/// Saved DiffMachine state can be self-referential, so we need to be careful about how we save it. All self-referential data is a link between
|
||||
@@ -211,6 +216,7 @@ impl Scheduler {
|
||||
|
||||
Self {
|
||||
pool,
|
||||
|
||||
receiver,
|
||||
|
||||
async_tasks: FuturesUnordered::new(),
|
||||
@@ -219,7 +225,6 @@ impl Scheduler {
|
||||
|
||||
heuristics,
|
||||
|
||||
// a storage for our receiver to dump into
|
||||
ui_events: VecDeque::new(),
|
||||
|
||||
pending_immediates: VecDeque::new(),
|
||||
@@ -230,7 +235,7 @@ impl Scheduler {
|
||||
|
||||
current_priority: EventPriority::Low,
|
||||
|
||||
// a dedicated fiber for each priority
|
||||
// sorted high to low by priority (0 = immediate, 3 = low)
|
||||
lanes: [
|
||||
PriorityLane::new(),
|
||||
PriorityLane::new(),
|
||||
@@ -248,25 +253,69 @@ impl Scheduler {
|
||||
|
||||
// Converts UI events into dirty scopes with various priorities
|
||||
pub fn consume_pending_events(&mut self) {
|
||||
// consume all events that are "continuous" to be batched
|
||||
// if we run into a discrete event, then bail early
|
||||
|
||||
while let Some(trigger) = self.ui_events.pop_back() {
|
||||
if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {
|
||||
if let Some(element) = trigger.mounted_dom_id {
|
||||
let priority = match &trigger.event {
|
||||
SyntheticEvent::ClipboardEvent(_) => {}
|
||||
SyntheticEvent::CompositionEvent(_) => {}
|
||||
SyntheticEvent::KeyboardEvent(_) => {}
|
||||
SyntheticEvent::FocusEvent(_) => {}
|
||||
SyntheticEvent::FormEvent(_) => {}
|
||||
SyntheticEvent::SelectionEvent(_) => {}
|
||||
SyntheticEvent::TouchEvent(_) => {}
|
||||
SyntheticEvent::WheelEvent(_) => {}
|
||||
SyntheticEvent::MediaEvent(_) => {}
|
||||
SyntheticEvent::AnimationEvent(_) => {}
|
||||
SyntheticEvent::TransitionEvent(_) => {}
|
||||
SyntheticEvent::ToggleEvent(_) => {}
|
||||
SyntheticEvent::MouseEvent(_) => {}
|
||||
SyntheticEvent::PointerEvent(_) => {}
|
||||
SyntheticEvent::GenericEvent(_) => {}
|
||||
let priority = match trigger.name {
|
||||
// clipboard
|
||||
"copy" | "cut" | "paste" => EventPriority::Medium,
|
||||
|
||||
// Composition
|
||||
"compositionend" | "compositionstart" | "compositionupdate" => {
|
||||
EventPriority::Low
|
||||
}
|
||||
|
||||
// Keyboard
|
||||
"keydown" | "keypress" | "keyup" => EventPriority::Low,
|
||||
|
||||
// Focus
|
||||
"focus" | "blur" => EventPriority::Low,
|
||||
|
||||
// Form
|
||||
"change" | "input" | "invalid" | "reset" | "submit" => EventPriority::Low,
|
||||
|
||||
// Mouse
|
||||
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend"
|
||||
| "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart"
|
||||
| "drop" | "mousedown" | "mouseenter" | "mouseleave" | "mousemove"
|
||||
| "mouseout" | "mouseover" | "mouseup" => EventPriority::Low,
|
||||
|
||||
// Pointer
|
||||
"pointerdown" | "pointermove" | "pointerup" | "pointercancel"
|
||||
| "gotpointercapture" | "lostpointercapture" | "pointerenter"
|
||||
| "pointerleave" | "pointerover" | "pointerout" => EventPriority::Low,
|
||||
|
||||
// Selection
|
||||
"select" | "touchcancel" | "touchend" => EventPriority::Low,
|
||||
|
||||
// Touch
|
||||
"touchmove" | "touchstart" => EventPriority::Low,
|
||||
|
||||
// Wheel
|
||||
"scroll" | "wheel" => EventPriority::Low,
|
||||
|
||||
// Media
|
||||
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied"
|
||||
| "encrypted" | "ended" | "error" | "loadeddata" | "loadedmetadata"
|
||||
| "loadstart" | "pause" | "play" | "playing" | "progress"
|
||||
| "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
|
||||
| "timeupdate" | "volumechange" | "waiting" => EventPriority::Low,
|
||||
|
||||
// Animation
|
||||
"animationstart" | "animationend" | "animationiteration" => {
|
||||
EventPriority::Low
|
||||
}
|
||||
|
||||
// Transition
|
||||
"transitionend" => EventPriority::Low,
|
||||
|
||||
// Toggle
|
||||
"toggle" => EventPriority::Low,
|
||||
|
||||
_ => EventPriority::Low,
|
||||
};
|
||||
|
||||
scope.call_listener(trigger.event, element);
|
||||
@@ -341,7 +390,6 @@ impl Scheduler {
|
||||
self.manually_poll_events();
|
||||
|
||||
if !self.has_any_work() {
|
||||
self.clean_up_garbage();
|
||||
return committed_mutations;
|
||||
}
|
||||
|
||||
@@ -349,7 +397,7 @@ impl Scheduler {
|
||||
|
||||
while self.has_any_work() {
|
||||
self.shift_priorities();
|
||||
self.work_on_current_lane(&mut || false, &mut committed_mutations);
|
||||
self.work_on_current_lane(|| false, &mut committed_mutations);
|
||||
}
|
||||
|
||||
committed_mutations
|
||||
@@ -393,7 +441,6 @@ impl Scheduler {
|
||||
|
||||
// Wait for any new events if we have nothing to do
|
||||
if !self.has_any_work() {
|
||||
self.clean_up_garbage();
|
||||
let deadline_expired = self.wait_for_any_trigger(&mut deadline_reached).await;
|
||||
|
||||
if deadline_expired {
|
||||
@@ -425,7 +472,7 @@ impl Scheduler {
|
||||
/// Returns true if the lane is finished before the deadline could be met.
|
||||
pub fn work_on_current_lane(
|
||||
&mut self,
|
||||
deadline_reached: &mut impl FnMut() -> bool,
|
||||
deadline_reached: impl FnMut() -> bool,
|
||||
mutations: &mut Vec<Mutations>,
|
||||
) -> bool {
|
||||
// Work through the current subtree, and commit the results when it finishes
|
||||
@@ -448,7 +495,7 @@ impl Scheduler {
|
||||
if let Some(scope) = self.current_lane().dirty_scopes.pop() {
|
||||
let component = self.pool.get_scope(scope).unwrap();
|
||||
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
||||
machine.stack.push(DiffInstruction::DiffNode { new, old });
|
||||
machine.stack.push(DiffInstruction::Diff { new, old });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,61 +584,6 @@ impl Scheduler {
|
||||
fn collect_garbage(&mut self, id: ElementId) {
|
||||
//
|
||||
}
|
||||
|
||||
pub fn clean_up_garbage(&mut self) {
|
||||
let mut scopes_to_kill = Vec::new();
|
||||
let mut garbage_list = Vec::new();
|
||||
|
||||
for scope in self.garbage_scopes.drain() {
|
||||
let scope = self.pool.get_scope_mut(scope).unwrap();
|
||||
for node in scope.consume_garbage() {
|
||||
garbage_list.push(node);
|
||||
}
|
||||
|
||||
while let Some(node) = garbage_list.pop() {
|
||||
match &node {
|
||||
VNode::Text(_) => {
|
||||
self.pool.collect_garbage(node.mounted_id());
|
||||
}
|
||||
VNode::Anchor(_) => {
|
||||
self.pool.collect_garbage(node.mounted_id());
|
||||
}
|
||||
VNode::Suspended(_) => {
|
||||
self.pool.collect_garbage(node.mounted_id());
|
||||
}
|
||||
|
||||
VNode::Element(el) => {
|
||||
self.pool.collect_garbage(node.mounted_id());
|
||||
for child in el.children {
|
||||
garbage_list.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
VNode::Fragment(frag) => {
|
||||
for child in frag.children {
|
||||
garbage_list.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
VNode::Component(comp) => {
|
||||
// TODO: run the hook destructors and then even delete the scope
|
||||
|
||||
let scope_id = comp.associated_scope.get().unwrap();
|
||||
let scope = self.pool.get_scope(scope_id).unwrap();
|
||||
let root = scope.root();
|
||||
|
||||
garbage_list.push(root);
|
||||
scopes_to_kill.push(scope_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for scope in scopes_to_kill.drain(..) {
|
||||
//
|
||||
// kill em
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PriorityLane {
|
||||
@@ -619,8 +611,8 @@ impl PriorityLane {
|
||||
}
|
||||
|
||||
pub struct TaskHandle {
|
||||
pub sender: UnboundedSender<SchedulerMsg>,
|
||||
pub our_id: u64,
|
||||
pub(crate) sender: UnboundedSender<SchedulerMsg>,
|
||||
pub(crate) our_id: u64,
|
||||
}
|
||||
|
||||
impl TaskHandle {
|
||||
|
||||
@@ -31,7 +31,6 @@ pub struct Scope {
|
||||
pub(crate) frames: ActiveFrame,
|
||||
pub(crate) caller: Rc<WrappedCaller>,
|
||||
pub(crate) child_nodes: ScopeChildren<'static>,
|
||||
pub(crate) pending_garbage: RefCell<Vec<*const VNode<'static>>>,
|
||||
|
||||
// Listeners
|
||||
pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
|
||||
@@ -90,7 +89,6 @@ impl Scope {
|
||||
listeners: Default::default(),
|
||||
borrowed_props: Default::default(),
|
||||
descendents: Default::default(),
|
||||
pending_garbage: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +108,6 @@ impl Scope {
|
||||
// Cycle to the next frame and then reset it
|
||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||
// Remove all the outdated listeners
|
||||
|
||||
self.ensure_drop_safety(pool);
|
||||
|
||||
// Safety:
|
||||
@@ -146,12 +143,6 @@ impl Scope {
|
||||
/// Refrences to hook data can only be stored in listeners and component props. During diffing, we make sure to log
|
||||
/// all listeners and borrowed props so we can clear them here.
|
||||
pub(crate) fn ensure_drop_safety(&mut self, pool: &ResourcePool) {
|
||||
// make sure all garabge is collected before trying to proceed with anything else
|
||||
debug_assert!(
|
||||
self.pending_garbage.borrow().is_empty(),
|
||||
"clean up your garabge please"
|
||||
);
|
||||
|
||||
// todo!("arch changes");
|
||||
|
||||
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
|
||||
@@ -235,19 +226,6 @@ impl Scope {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn consume_garbage(&self) -> Vec<&VNode> {
|
||||
self.pending_garbage
|
||||
.borrow_mut()
|
||||
.drain(..)
|
||||
.map(|node| {
|
||||
// safety: scopes cannot cycle without their garbage being collected. these nodes are safe
|
||||
let node: &VNode<'static> = unsafe { &*node };
|
||||
let node: &VNode = unsafe { std::mem::transmute(node) };
|
||||
node
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn root(&self) -> &VNode {
|
||||
self.frames.fin_head()
|
||||
}
|
||||
|
||||
@@ -20,17 +20,25 @@ impl TestDom {
|
||||
NodeFactory::new(&self.bump)
|
||||
}
|
||||
|
||||
pub fn render<'a, F>(&'a self, lazy_nodes: LazyNodes<'a, F>) -> VNode<'a>
|
||||
pub fn render_direct<'a, F>(&'a self, lazy_nodes: LazyNodes<'a, F>) -> VNode<'a>
|
||||
where
|
||||
F: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
lazy_nodes.into_vnode(NodeFactory::new(&self.bump))
|
||||
}
|
||||
|
||||
pub fn render<'a, F>(&'a self, lazy_nodes: LazyNodes<'a, F>) -> &'a VNode<'a>
|
||||
where
|
||||
F: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
self.bump
|
||||
.alloc(lazy_nodes.into_vnode(NodeFactory::new(&self.bump)))
|
||||
}
|
||||
|
||||
pub fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
|
||||
let mutations = Mutations::new();
|
||||
let mut machine = DiffMachine::new(mutations, &self.scheduler.pool);
|
||||
machine.stack.push(DiffInstruction::DiffNode { new, old });
|
||||
machine.stack.push(DiffInstruction::Diff { new, old });
|
||||
machine.mutations
|
||||
}
|
||||
|
||||
@@ -38,7 +46,7 @@ impl TestDom {
|
||||
where
|
||||
F1: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
let old = self.bump.alloc(self.render(left));
|
||||
let old = self.bump.alloc(self.render_direct(left));
|
||||
|
||||
let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
|
||||
|
||||
@@ -58,20 +66,18 @@ impl TestDom {
|
||||
F1: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
F2: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
let old = self.bump.alloc(self.render(left));
|
||||
|
||||
let new = self.bump.alloc(self.render(right));
|
||||
let (old, new) = (self.render(left), self.render(right));
|
||||
|
||||
let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
|
||||
|
||||
machine.stack.create_node(old, MountType::Append);
|
||||
|
||||
machine.work(&mut || false);
|
||||
machine.work(|| false);
|
||||
let create_edits = machine.mutations;
|
||||
|
||||
let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
|
||||
|
||||
machine.stack.push(DiffInstruction::DiffNode { old, new });
|
||||
machine.stack.push(DiffInstruction::Diff { old, new });
|
||||
|
||||
machine.work(&mut || false);
|
||||
|
||||
@@ -80,3 +86,9 @@ impl TestDom {
|
||||
(create_edits, edits)
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualDom {
|
||||
pub fn simulate(&mut self) {
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
//! # VirtualDOM Implementation for Rust
|
||||
//!
|
||||
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
|
||||
//!
|
||||
//! In this file, multiple items are defined. This file is big, but should be documented well to
|
||||
@@ -20,21 +21,38 @@
|
||||
|
||||
use crate::innerlude::*;
|
||||
use futures_util::{Future, FutureExt};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
};
|
||||
use std::{any::Any, pin::Pin};
|
||||
|
||||
/// An integrated virtual node system that progresses events and diffs UI trees.
|
||||
///
|
||||
/// Differences are converted into patches which a renderer can use to draw the UI.
|
||||
///
|
||||
/// If you are building an App with Dioxus, you probably won't want to reach for this directly, instead opting to defer
|
||||
/// to a particular crate's wrapper over the [`VirtualDom`] API.
|
||||
///
|
||||
/// Example
|
||||
/// ```rust
|
||||
/// static App: FC<()> = |cx| {
|
||||
/// cx.render(rsx!{
|
||||
/// div {
|
||||
/// "Hello World"
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// async fn main() {
|
||||
/// let mut dom = VirtualDom::new(App);
|
||||
/// let mut inital_edits = dom.rebuild();
|
||||
/// initialize_screen(inital_edits);
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
/// loop {
|
||||
/// let next_frame = TimeoutFuture::new(Duration::from_millis(16));
|
||||
/// let edits = dom.run_with_deadline(next_frame).await;
|
||||
/// apply_edits(edits);
|
||||
/// render_frame();
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub struct VirtualDom {
|
||||
scheduler: Scheduler,
|
||||
|
||||
@@ -42,7 +60,7 @@ pub struct VirtualDom {
|
||||
|
||||
root_fc: Box<dyn Any>,
|
||||
|
||||
root_props: Pin<Box<dyn std::any::Any>>,
|
||||
root_props: Pin<Box<dyn Any>>,
|
||||
}
|
||||
|
||||
impl VirtualDom {
|
||||
@@ -178,28 +196,22 @@ impl VirtualDom {
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
|
||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch
|
||||
///
|
||||
/// The diff machine expects the RealDom's stack to be the root of the application
|
||||
/// The diff machine expects the RealDom's stack to be the root of the application.
|
||||
///
|
||||
/// Events like garabge collection, application of refs, etc are not handled by this method and can only be progressed
|
||||
/// through "run". We completely avoid the task scheduler infrastructure.
|
||||
/// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the
|
||||
/// root component will be ran once and then diffed. All updates will flow out as mutations.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// static App: FC<()> = |cx| cx.render(rsx!{ "hello world" });
|
||||
/// let mut dom = VirtualDom::new();
|
||||
/// let edits = dom.rebuild();
|
||||
///
|
||||
/// apply_edits(edits);
|
||||
/// ```
|
||||
pub fn rebuild<'s>(&'s mut self) -> Mutations<'s> {
|
||||
let mut fut = self.rebuild_async().boxed_local();
|
||||
|
||||
loop {
|
||||
if let Some(edits) = (&mut fut).now_or_never() {
|
||||
break edits;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rebuild the dom from the ground up
|
||||
///
|
||||
/// This method is asynchronous to prevent the application from blocking while the dom is being rebuilt. Computing
|
||||
/// the diff and creating nodes can be expensive, so we provide this method to avoid blocking the main thread. This
|
||||
/// method can be useful when needing to perform some crucial periodic tasks.
|
||||
pub async fn rebuild_async<'s>(&'s mut self) -> Mutations<'s> {
|
||||
let mut shared = self.scheduler.pool.clone();
|
||||
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
|
||||
|
||||
@@ -214,9 +226,10 @@ impl VirtualDom {
|
||||
diff_machine
|
||||
.stack
|
||||
.create_node(cur_component.frames.fin_head(), MountType::Append);
|
||||
|
||||
diff_machine.stack.scope_stack.push(self.base_scope);
|
||||
|
||||
// let completed = diff_machine.work();
|
||||
diff_machine.work(|| false);
|
||||
} else {
|
||||
// todo: should this be a hard error?
|
||||
log::warn!(
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Testing of Dioxus core
|
||||
|
||||
|
||||
NodeFactory
|
||||
- [] rsx, html, NodeFactory generate the same structures
|
||||
|
||||
Diffing
|
||||
- [x] create elements
|
||||
- [x] create text
|
||||
- [x] create fragments
|
||||
- [x] create empty fragments (placeholders)
|
||||
- [x] diff elements
|
||||
- [x] diff from element/text to fragment
|
||||
- [x] diff from element/text to empty fragment
|
||||
- [x] diff to element with children works too
|
||||
- [x] replace with works forward
|
||||
- [x] replace with works backward
|
||||
- [x] un-keyed diffing
|
||||
- [x] keyed diffing
|
||||
- [x] keyed diffing out of order
|
||||
- [x] keyed diffing with prefix/suffix
|
||||
- [x] suspended nodes work
|
||||
|
||||
Lifecycle
|
||||
- [] Components mount properly
|
||||
- [] Components create new child components
|
||||
- [] Replaced components unmount old components and mount new
|
||||
- [] Post-render effects are called
|
||||
- []
|
||||
|
||||
|
||||
Shared Context
|
||||
- [] Shared context propagates downwards
|
||||
- [] unwrapping shared context if it doesn't exist works too
|
||||
|
||||
Suspense
|
||||
- [] use_suspense generates suspended nodes
|
||||
|
||||
|
||||
Hooks
|
||||
- [] Drop order is maintained
|
||||
- [] Shared hook state is okay
|
||||
- [] use_hook works
|
||||
- [] use_ref works
|
||||
- [] use_noderef works
|
||||
- [] use_provide_state
|
||||
- [] use_consume_state
|
||||
|
||||
|
||||
VirtualDOM API
|
||||
- [] work
|
||||
- [] rebuild
|
||||
- [] change props
|
||||
@@ -1,19 +0,0 @@
|
||||
use futures_channel::mpsc::unbounded;
|
||||
|
||||
#[async_std::test]
|
||||
async fn channels() {
|
||||
let (sender, mut receiver) = unbounded::<u32>();
|
||||
|
||||
// drop(sender);
|
||||
|
||||
match receiver.try_next() {
|
||||
Ok(a) => {
|
||||
dbg!(a);
|
||||
}
|
||||
Err(no) => {
|
||||
dbg!(no);
|
||||
}
|
||||
}
|
||||
|
||||
sender.unbounded_send(1).unwrap();
|
||||
}
|
||||
@@ -1,17 +1,23 @@
|
||||
//! tests to prove that the iterative implementation works
|
||||
//! Prove that the dom works normally through virtualdom methods.
|
||||
//! This methods all use "rebuild" which completely bypasses the scheduler.
|
||||
//! Hard rebuilds don't consume any events from the event queue.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use dioxus::{prelude::*, DomEdit, Mutations};
|
||||
mod test_logging;
|
||||
use dioxus::{prelude::*, DomEdit};
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
mod test_logging;
|
||||
use DomEdit::*;
|
||||
|
||||
const LOGGING_ENABLED: bool = false;
|
||||
fn new_dom<P: Properties + 'static>(app: FC<P>, props: P) -> VirtualDom {
|
||||
const IS_LOGGING_ENABLED: bool = false;
|
||||
test_logging::set_up_logging(IS_LOGGING_ENABLED);
|
||||
VirtualDom::new_with_props(app, props)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_original_diff() {
|
||||
static App: FC<()> = |cx| {
|
||||
static APP: FC<()> = |cx| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
div {
|
||||
@@ -21,7 +27,7 @@ fn test_original_diff() {
|
||||
})
|
||||
};
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mut dom = new_dom(APP, ());
|
||||
let mutations = dom.rebuild();
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
@@ -41,7 +47,7 @@ fn test_original_diff() {
|
||||
|
||||
#[async_std::test]
|
||||
async fn create() {
|
||||
static App: FC<()> = |cx| {
|
||||
static APP: FC<()> = |cx| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
div {
|
||||
@@ -59,9 +65,9 @@ async fn create() {
|
||||
})
|
||||
};
|
||||
|
||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mutations = dom.rebuild_async().await;
|
||||
let mut dom = new_dom(APP, ());
|
||||
let mutations = dom.rebuild();
|
||||
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
@@ -92,7 +98,7 @@ async fn create() {
|
||||
|
||||
#[async_std::test]
|
||||
async fn create_list() {
|
||||
static App: FC<()> = |cx| {
|
||||
static APP: FC<()> = |cx| {
|
||||
cx.render(rsx! {
|
||||
{(0..3).map(|f| rsx!{ div {
|
||||
"hello"
|
||||
@@ -100,10 +106,8 @@ async fn create_list() {
|
||||
})
|
||||
};
|
||||
|
||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mutations = dom.rebuild_async().await;
|
||||
let mut dom = new_dom(APP, ());
|
||||
let mutations = dom.rebuild();
|
||||
|
||||
// copilot wrote this test :P
|
||||
assert_eq!(
|
||||
@@ -134,7 +138,7 @@ async fn create_list() {
|
||||
|
||||
#[async_std::test]
|
||||
async fn create_simple() {
|
||||
static App: FC<()> = |cx| {
|
||||
static APP: FC<()> = |cx| {
|
||||
cx.render(rsx! {
|
||||
div {}
|
||||
div {}
|
||||
@@ -143,10 +147,8 @@ async fn create_simple() {
|
||||
})
|
||||
};
|
||||
|
||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mutations = dom.rebuild_async().await;
|
||||
let mut dom = new_dom(APP, ());
|
||||
let mutations = dom.rebuild();
|
||||
|
||||
// copilot wrote this test :P
|
||||
assert_eq!(
|
||||
@@ -179,10 +181,8 @@ async fn create_components() {
|
||||
})
|
||||
};
|
||||
|
||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mutations = dom.rebuild_async().await;
|
||||
let mut dom = new_dom(App, ());
|
||||
let mutations = dom.rebuild();
|
||||
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
@@ -225,10 +225,8 @@ async fn anchors() {
|
||||
})
|
||||
};
|
||||
|
||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mutations = dom.rebuild_async().await;
|
||||
let mut dom = new_dom(App, ());
|
||||
let mutations = dom.rebuild();
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
@@ -247,14 +245,18 @@ async fn anchors() {
|
||||
#[async_std::test]
|
||||
async fn suspended() {
|
||||
static App: FC<()> = |cx| {
|
||||
let val = use_suspense(cx, || async {}, |cx, _| cx.render(rsx! { "hi "}));
|
||||
let val = use_suspense(
|
||||
cx,
|
||||
|| async {
|
||||
//
|
||||
},
|
||||
|cx, _| cx.render(rsx! { "hi "}),
|
||||
);
|
||||
cx.render(rsx! { {val} })
|
||||
};
|
||||
|
||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mutations = dom.rebuild_async().await;
|
||||
let mut dom = new_dom(App, ());
|
||||
let mutations = dom.rebuild();
|
||||
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
@@ -1,2 +0,0 @@
|
||||
/// A virtualdom wrapper used for testing purposes.
|
||||
pub struct DebugDiff {}
|
||||
@@ -1,48 +0,0 @@
|
||||
//! tests to prove that the iterative implementation works
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
mod test_logging;
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
const LOGGING_ENABLED: bool = false;
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_iterative_create_components() {
|
||||
static App: FC<()> = |cx| {
|
||||
// test root fragments
|
||||
cx.render(rsx! {
|
||||
Child { "abc1" }
|
||||
Child { "abc2" }
|
||||
Child { "abc3" }
|
||||
})
|
||||
};
|
||||
|
||||
fn Child(cx: Context<()>) -> DomTree {
|
||||
// test root fragments, anchors, and ChildNode type
|
||||
cx.render(rsx! {
|
||||
h1 {}
|
||||
div { {cx.children()} }
|
||||
Fragment {
|
||||
Fragment {
|
||||
Fragment {
|
||||
"wozza"
|
||||
}
|
||||
}
|
||||
}
|
||||
{(0..0).map(|_f| rsx!{ div { "walalla"}})}
|
||||
p {}
|
||||
})
|
||||
}
|
||||
|
||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
|
||||
let mutations = dom.rebuild_async().await;
|
||||
dbg!(mutations);
|
||||
|
||||
let mutations = dom.diff();
|
||||
dbg!(mutations);
|
||||
}
|
||||
@@ -4,24 +4,19 @@
|
||||
//!
|
||||
//! It does not validated that component lifecycles work properly. This is done in another test file.
|
||||
|
||||
use dioxus::{prelude::*, DomEdit, TestDom};
|
||||
use dioxus::{nodes::VSuspended, prelude::*, DomEdit, TestDom};
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
mod test_logging;
|
||||
use DomEdit::*;
|
||||
|
||||
// logging is wired up to the test harness
|
||||
// feel free to enable while debugging
|
||||
const IS_LOGGING_ENABLED: bool = false;
|
||||
|
||||
fn new_dom() -> TestDom {
|
||||
const IS_LOGGING_ENABLED: bool = false;
|
||||
test_logging::set_up_logging(IS_LOGGING_ENABLED);
|
||||
TestDom::new()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diffing_works() {}
|
||||
use DomEdit::*;
|
||||
|
||||
/// Should push the text node onto the stack and modify it
|
||||
#[test]
|
||||
@@ -98,8 +93,8 @@ fn fragments_create_properly() {
|
||||
fn empty_fragments_create_anchors() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
|
||||
let right = rsx!({ (0..1).map(|f| rsx! { div {}}) });
|
||||
let left = rsx!({ (0..0).map(|_f| rsx! { div {}}) });
|
||||
let right = rsx!({ (0..1).map(|_f| rsx! { div {}}) });
|
||||
|
||||
let (create, change) = dom.lazy_diff(left, right);
|
||||
|
||||
@@ -121,8 +116,8 @@ fn empty_fragments_create_anchors() {
|
||||
fn empty_fragments_create_many_anchors() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({ (0..0).map(|f| rsx! { div {}}) });
|
||||
let right = rsx!({ (0..5).map(|f| rsx! { div {}}) });
|
||||
let left = rsx!({ (0..0).map(|_f| rsx! { div {}}) });
|
||||
let right = rsx!({ (0..5).map(|_f| rsx! { div {}}) });
|
||||
|
||||
let (create, change) = dom.lazy_diff(left, right);
|
||||
assert_eq!(
|
||||
@@ -148,7 +143,7 @@ fn empty_fragments_create_many_anchors() {
|
||||
fn empty_fragments_create_anchors_with_many_children() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({ (0..0).map(|f| rsx! { div {} }) });
|
||||
let left = rsx!({ (0..0).map(|_| rsx! { div {} }) });
|
||||
let right = rsx!({
|
||||
(0..3).map(|f| {
|
||||
rsx! { div { "hello: {f}" }}
|
||||
@@ -192,11 +187,11 @@ fn many_items_become_fragment() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
(0..2).map(|f| {
|
||||
(0..2).map(|_| {
|
||||
rsx! { div { "hello" }}
|
||||
})
|
||||
});
|
||||
let right = rsx!({ (0..0).map(|f| rsx! { div {} }) });
|
||||
let right = rsx!({ (0..0).map(|_| rsx! { div {} }) });
|
||||
|
||||
let (create, change) = dom.lazy_diff(left, right);
|
||||
assert_eq!(
|
||||
@@ -235,17 +230,17 @@ fn two_equal_fragments_are_equal() {
|
||||
let dom = new_dom();
|
||||
|
||||
let left = rsx!({
|
||||
(0..2).map(|f| {
|
||||
(0..2).map(|_| {
|
||||
rsx! { div { "hello" }}
|
||||
})
|
||||
});
|
||||
let right = rsx!({
|
||||
(0..2).map(|f| {
|
||||
(0..2).map(|_| {
|
||||
rsx! { div { "hello" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (create, change) = dom.lazy_diff(left, right);
|
||||
let (_create, change) = dom.lazy_diff(left, right);
|
||||
assert!(change.edits.is_empty());
|
||||
}
|
||||
|
||||
@@ -263,7 +258,7 @@ fn two_fragments_with_differrent_elements_are_differet() {
|
||||
p {}
|
||||
);
|
||||
|
||||
let (create, changes) = dom.lazy_diff(left, right);
|
||||
let (_create, changes) = dom.lazy_diff(left, right);
|
||||
log::debug!("{:#?}", &changes);
|
||||
assert_eq!(
|
||||
changes.edits,
|
||||
@@ -700,3 +695,21 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suspense() {
|
||||
let dom = new_dom();
|
||||
|
||||
let edits = dom.create(LazyNodes::new(|f| {
|
||||
use std::cell::{Cell, RefCell};
|
||||
VNode::Suspended(f.bump().alloc(VSuspended {
|
||||
task_id: 0,
|
||||
callback: RefCell::new(None),
|
||||
dom_id: Cell::new(None),
|
||||
}))
|
||||
}));
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[CreatePlaceholder { id: 0 }, AppendChildren { many: 1 }]
|
||||
);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
// type VirtualNode = VNode;
|
||||
|
||||
/// Test a basic usage of a virtual dom + text renderer combo
|
||||
#[test]
|
||||
fn simple_integration() {}
|
||||
@@ -0,0 +1 @@
|
||||
//! Tests for the lifecycle of components.
|
||||
@@ -0,0 +1 @@
|
||||
//! Tests for the scheduler.
|
||||
@@ -1,30 +0,0 @@
|
||||
//! Diffing is interruptible, but uses yield_now which is loop-pollable
|
||||
//!
|
||||
//! This means you can actually call it synchronously if you want.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use dioxus::{prelude::*, scope::Scope};
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use futures_util::FutureExt;
|
||||
|
||||
#[test]
|
||||
fn worksync() {
|
||||
static App: FC<()> = |cx| {
|
||||
cx.render(rsx! {
|
||||
div {"hello"}
|
||||
})
|
||||
};
|
||||
let mut dom = VirtualDom::new(App);
|
||||
|
||||
let mut fut = dom.rebuild_async().boxed_local();
|
||||
|
||||
let mutations = loop {
|
||||
let g = (&mut fut).now_or_never();
|
||||
if g.is_some() {
|
||||
break g.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
dbg!(mutations);
|
||||
}
|
||||
@@ -65,10 +65,6 @@ features = [
|
||||
"HtmlOptionElement",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 's'
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
|
||||
+79
-79
@@ -582,85 +582,85 @@ pub fn load_document() -> Document {
|
||||
|
||||
pub fn event_name_from_typ(typ: &str) -> &'static str {
|
||||
match typ {
|
||||
"copy" => "oncopy",
|
||||
"cut" => "oncut",
|
||||
"paste" => "onpaste",
|
||||
"compositionend" => "oncompositionend",
|
||||
"compositionstart" => "oncompositionstart",
|
||||
"compositionupdate" => "oncompositionupdate",
|
||||
"keydown" => "onkeydown",
|
||||
"keypress" => "onkeypress",
|
||||
"keyup" => "onkeyup",
|
||||
"focus" => "onfocus",
|
||||
"blur" => "onblur",
|
||||
"change" => "onchange",
|
||||
"input" => "oninput",
|
||||
"invalid" => "oninvalid",
|
||||
"reset" => "onreset",
|
||||
"submit" => "onsubmit",
|
||||
"click" => "onclick",
|
||||
"contextmenu" => "oncontextmenu",
|
||||
"doubleclick" => "ondoubleclick",
|
||||
"drag" => "ondrag",
|
||||
"dragend" => "ondragend",
|
||||
"dragenter" => "ondragenter",
|
||||
"dragexit" => "ondragexit",
|
||||
"dragleave" => "ondragleave",
|
||||
"dragover" => "ondragover",
|
||||
"dragstart" => "ondragstart",
|
||||
"drop" => "ondrop",
|
||||
"mousedown" => "onmousedown",
|
||||
"mouseenter" => "onmouseenter",
|
||||
"mouseleave" => "onmouseleave",
|
||||
"mousemove" => "onmousemove",
|
||||
"mouseout" => "onmouseout",
|
||||
"mouseover" => "onmouseover",
|
||||
"mouseup" => "onmouseup",
|
||||
"pointerdown" => "onpointerdown",
|
||||
"pointermove" => "onpointermove",
|
||||
"pointerup" => "onpointerup",
|
||||
"pointercancel" => "onpointercancel",
|
||||
"gotpointercapture" => "ongotpointercapture",
|
||||
"lostpointercapture" => "onlostpointercapture",
|
||||
"pointerenter" => "onpointerenter",
|
||||
"pointerleave" => "onpointerleave",
|
||||
"pointerover" => "onpointerover",
|
||||
"pointerout" => "onpointerout",
|
||||
"select" => "onselect",
|
||||
"touchcancel" => "ontouchcancel",
|
||||
"touchend" => "ontouchend",
|
||||
"touchmove" => "ontouchmove",
|
||||
"touchstart" => "ontouchstart",
|
||||
"scroll" => "onscroll",
|
||||
"wheel" => "onwheel",
|
||||
"animationstart" => "onanimationstart",
|
||||
"animationend" => "onanimationend",
|
||||
"animationiteration" => "onanimationiteration",
|
||||
"transitionend" => "ontransitionend",
|
||||
"abort" => "onabort",
|
||||
"canplay" => "oncanplay",
|
||||
"canplaythrough" => "oncanplaythrough",
|
||||
"durationchange" => "ondurationchange",
|
||||
"emptied" => "onemptied",
|
||||
"encrypted" => "onencrypted",
|
||||
"ended" => "onended",
|
||||
"error" => "onerror",
|
||||
"loadeddata" => "onloadeddata",
|
||||
"loadedmetadata" => "onloadedmetadata",
|
||||
"loadstart" => "onloadstart",
|
||||
"pause" => "onpause",
|
||||
"play" => "onplay",
|
||||
"playing" => "onplaying",
|
||||
"progress" => "onprogress",
|
||||
"ratechange" => "onratechange",
|
||||
"seeked" => "onseeked",
|
||||
"seeking" => "onseeking",
|
||||
"stalled" => "onstalled",
|
||||
"suspend" => "onsuspend",
|
||||
"timeupdate" => "ontimeupdate",
|
||||
"volumechange" => "onvolumechange",
|
||||
"waiting" => "onwaiting",
|
||||
"toggle" => "ontoggle",
|
||||
"copy" => "copy",
|
||||
"cut" => "cut",
|
||||
"paste" => "paste",
|
||||
"compositionend" => "compositionend",
|
||||
"compositionstart" => "compositionstart",
|
||||
"compositionupdate" => "compositionupdate",
|
||||
"keydown" => "keydown",
|
||||
"keypress" => "keypress",
|
||||
"keyup" => "keyup",
|
||||
"focus" => "focus",
|
||||
"blur" => "blur",
|
||||
"change" => "change",
|
||||
"input" => "input",
|
||||
"invalid" => "invalid",
|
||||
"reset" => "reset",
|
||||
"submit" => "submit",
|
||||
"click" => "click",
|
||||
"contextmenu" => "contextmenu",
|
||||
"doubleclick" => "doubleclick",
|
||||
"drag" => "drag",
|
||||
"dragend" => "dragend",
|
||||
"dragenter" => "dragenter",
|
||||
"dragexit" => "dragexit",
|
||||
"dragleave" => "dragleave",
|
||||
"dragover" => "dragover",
|
||||
"dragstart" => "dragstart",
|
||||
"drop" => "drop",
|
||||
"mousedown" => "mousedown",
|
||||
"mouseenter" => "mouseenter",
|
||||
"mouseleave" => "mouseleave",
|
||||
"mousemove" => "mousemove",
|
||||
"mouseout" => "mouseout",
|
||||
"mouseover" => "mouseover",
|
||||
"mouseup" => "mouseup",
|
||||
"pointerdown" => "pointerdown",
|
||||
"pointermove" => "pointermove",
|
||||
"pointerup" => "pointerup",
|
||||
"pointercancel" => "pointercancel",
|
||||
"gotpointercapture" => "gotpointercapture",
|
||||
"lostpointercapture" => "lostpointercapture",
|
||||
"pointerenter" => "pointerenter",
|
||||
"pointerleave" => "pointerleave",
|
||||
"pointerover" => "pointerover",
|
||||
"pointerout" => "pointerout",
|
||||
"select" => "select",
|
||||
"touchcancel" => "touchcancel",
|
||||
"touchend" => "touchend",
|
||||
"touchmove" => "touchmove",
|
||||
"touchstart" => "touchstart",
|
||||
"scroll" => "scroll",
|
||||
"wheel" => "wheel",
|
||||
"animationstart" => "animationstart",
|
||||
"animationend" => "animationend",
|
||||
"animationiteration" => "animationiteration",
|
||||
"transitionend" => "transitionend",
|
||||
"abort" => "abort",
|
||||
"canplay" => "canplay",
|
||||
"canplaythrough" => "canplaythrough",
|
||||
"durationchange" => "durationchange",
|
||||
"emptied" => "emptied",
|
||||
"encrypted" => "encrypted",
|
||||
"ended" => "ended",
|
||||
"error" => "error",
|
||||
"loadeddata" => "loadeddata",
|
||||
"loadedmetadata" => "loadedmetadata",
|
||||
"loadstart" => "loadstart",
|
||||
"pause" => "pause",
|
||||
"play" => "play",
|
||||
"playing" => "playing",
|
||||
"progress" => "progress",
|
||||
"ratechange" => "ratechange",
|
||||
"seeked" => "seeked",
|
||||
"seeking" => "seeking",
|
||||
"stalled" => "stalled",
|
||||
"suspend" => "suspend",
|
||||
"timeupdate" => "timeupdate",
|
||||
"volumechange" => "volumechange",
|
||||
"waiting" => "waiting",
|
||||
"toggle" => "toggle",
|
||||
_ => {
|
||||
panic!("unsupported event type")
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user