use crate::{cached_set::CacheId, RootRender, VdomWeak}; use bumpalo::Bump; use std::fmt; use std::iter; use std::mem; use std::u32; /// A virtual DOM node. #[derive(Debug, Clone)] pub struct Node<'a> { #[cfg(feature = "xxx-unstable-internal-use-only")] #[doc(hidden)] pub kind: NodeKind<'a>, #[cfg(not(feature = "xxx-unstable-internal-use-only"))] pub(crate) kind: NodeKind<'a>, } pub_unstable_internal! { /// A node is either a text node or an element. #[derive(Debug, Clone)] pub(crate) enum NodeKind<'a> { /// A text node. Text(TextNode<'a>), /// An element potentially with attributes and children. Element(&'a ElementNode<'a>), /// A node in the vdom's `CachedSet`. This allows us to avoid /// re-rendering and re-diffing subtrees. Cached(CachedNode), } } pub_unstable_internal! { /// Text nodes are just a string of text. They cannot have attributes or /// children. #[derive(Debug, Clone)] pub(crate) struct TextNode<'a> { pub text: &'a str, } } pub_unstable_internal! { /// Elements have a tag name, zero or more attributes, and zero or more /// children. #[derive(Debug, Clone)] pub(crate) struct ElementNode<'a> { pub key: NodeKey, pub tag_name: &'a str, pub listeners: &'a [Listener<'a>], pub attributes: &'a [Attribute<'a>], pub children: &'a [Node<'a>], pub namespace: Option<&'a str>, } } pub_unstable_internal! { /// A cached node exists in an arena that is internal to the `Vdom`. It /// allows us to avoid both re-rendering a sub-tree and re-diffing /// it. `CachedNode`s cannot contain cycles, since they can only contain /// other `CachedNode`s that already exist when they are being constructed. #[derive(Debug, Clone, Copy)] pub(crate) struct CachedNode { pub id: CacheId, // Always a copy of the inner cached node's key. pub key: NodeKey, } } /// The key for keyed children. /// /// Keys must be unique among siblings. /// /// If any sibling is keyed, then they all must be keyed. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct NodeKey(pub(crate) u32); impl Default for NodeKey { fn default() -> NodeKey { NodeKey::NONE } } impl NodeKey { /// The default, lack of a key. pub const NONE: NodeKey = NodeKey(u32::MAX); /// Is this key `NodeKey::NONE`? #[inline] pub fn is_none(&self) -> bool { *self == Self::NONE } /// Is this key not `NodeKey::NONE`? #[inline] pub fn is_some(&self) -> bool { !self.is_none() } /// Create a new `NodeKey`. /// /// `key` must not be `u32::MAX`. #[inline] pub fn new(key: u32) -> Self { debug_assert_ne!(key, u32::MAX); NodeKey(key) } } /// An event listener callback function. /// /// It takes three parameters: /// /// 1. The virtual DOM's root rendering component. /// 2. A capability to scheduler virtual DOM re-rendering. /// 3. The event that occurred. pub(crate) type ListenerCallback<'a> = &'a (dyn Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static); /// An event listener. pub struct Listener<'a> { /// The type of event to listen for. pub(crate) event: &'a str, /// The callback to invoke when the event happens. pub(crate) callback: ListenerCallback<'a>, } /// An attribute on a DOM node, such as `id="my-thing"` or /// `href="https://example.com"`. #[derive(Clone, Debug)] pub struct Attribute<'a> { pub(crate) name: &'a str, pub(crate) value: &'a str, } impl<'a> From for Node<'a> { #[inline] fn from(c: CachedNode) -> Self { Node { kind: NodeKind::Cached(c), } } } impl fmt::Debug for Listener<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let (a, b) = self.get_callback_parts(); let a = a as *mut u32; let b = b as *mut u32; f.debug_struct("Listener") .field("event", &self.event) .field("callback", &(a, b)) .finish() } } impl<'a> Attribute<'a> { /// Get this attribute's name, such as `"id"` in `
`. #[inline] pub fn name(&self) -> &'a str { self.name } /// The attribute value, such as `"my-thing"` in `
`. #[inline] pub fn value(&self) -> &'a str { self.value } /// Certain attributes are considered "volatile" and can change via user /// input that we can't see when diffing against the old virtual DOM. For /// these attributes, we want to always re-set the attribute on the physical /// DOM node, even if the old and new virtual DOM nodes have the same value. #[inline] pub(crate) fn is_volatile(&self) -> bool { match self.name { "value" | "checked" | "selected" => true, _ => false, } } } impl<'a> Node<'a> { /// Low-level constructor for making a new `Node` of type element with given /// parts. /// /// This is primarily intended for JSX and templating proc-macros to compile /// down into. If you are building nodes by-hand, prefer using the /// `dodrio::builder::*` APIs. #[inline] pub fn element( bump: &'a Bump, key: NodeKey, tag_name: &'a str, listeners: &'a [Listener<'a>], attributes: &'a [Attribute<'a>], children: &'a [Node<'a>], namespace: Option<&'a str>, ) -> Node<'a> { let element = bump.alloc_with(|| ElementNode { key, tag_name, listeners, attributes, children, namespace, }); Node { kind: NodeKind::Element(element), } } /// Construct a new text node with the given text. #[inline] pub(crate) fn text(text: &'a str) -> Node<'a> { Node { kind: NodeKind::Text(TextNode { text }), } } #[inline] pub(crate) fn key(&self) -> NodeKey { match &self.kind { NodeKind::Text(_) => NodeKey::NONE, NodeKind::Element(e) => e.key, NodeKind::Cached(c) => c.key, } } } /// A node can become an iterator that yields the node itself once. /// /// This implementation of `IntoIterator` mostly exists to improve the /// `typed-html` ergonomics, where the macro invokes `.into_iter()` on the child /// contents of a tag. By implementing `IntoIterator` here, we avoid having to /// do nasty shenanigans like `

vec![$contents]

` instead of plain old /// `

$contents

`. impl<'a> IntoIterator for Node<'a> { type Item = Node<'a>; type IntoIter = iter::Once; fn into_iter(self) -> Self::IntoIter { iter::once(self) } } union CallbackFatPtr<'a> { callback: ListenerCallback<'a>, parts: (u32, u32), } impl Listener<'_> { #[inline] pub(crate) fn get_callback_parts(&self) -> (u32, u32) { assert_eq!( mem::size_of::(), mem::size_of::() ); unsafe { let fat = CallbackFatPtr { callback: self.callback, }; let (a, b) = fat.parts; debug_assert!(a != 0); (a, b) } } }