on 3906f80 help sign in
dodrio/src/node.rs
227 lines 6.7 kB history raw
1use crate::{cached_set::CacheId, RootRender, VdomWeak};
2use bumpalo::Bump;
3use std::fmt;
4use std::iter;
5use std::marker::PhantomData;
6use std::mem;
8/// A virtual DOM node.
9#[derive(Debug, Clone)]
10pub struct Node<'a> {
11 #[cfg(feature = "xxx-unstable-internal-use-only")]
12 #[doc(hidden)]
13 pub kind: NodeKind<'a>,
15 #[cfg(not(feature = "xxx-unstable-internal-use-only"))]
16 pub(crate) kind: NodeKind<'a>,
19pub_unstable_internal! {
20 /// A node is either a text node or an element.
21 #[derive(Debug, Clone)]
22 pub(crate) enum NodeKind<'a> {
23 /// A text node.
24 Text(TextNode<'a>),
26 /// An element potentially with attributes and children.
27 Element(ElementNode<'a>),
29 /// A node in the vdom's `CachedSet`. This allows us to avoid
30 /// re-rendering and re-diffing subtrees.
31 Cached(CachedNode<'a>),
32 }
35pub_unstable_internal! {
36 /// Text nodes are just a string of text. They cannot have attributes or
37 /// children.
38 #[derive(Debug, Clone)]
39 pub(crate) struct TextNode<'a> {
40 pub text: &'a str,
41 }
44pub_unstable_internal! {
45 /// Elements have a tag name, zero or more attributes, and zero or more
46 /// children.
47 #[derive(Debug, Clone)]
48 pub(crate) struct ElementNode<'a> {
49 pub tag_name: &'a str,
50 pub listeners: &'a [Listener<'a>],
51 pub attributes: &'a [Attribute<'a>],
52 pub children: &'a [Node<'a>],
53 pub namespace: Option<&'a str>,
54 }
57pub_unstable_internal! {
58 /// A cached node exists in an arena that is internal to the `Vdom`. It
59 /// allows us to avoid both re-rendering a sub-tree and re-diffing
60 /// it. `CachedNode`s cannot contain cycles, since they can only contain
61 /// other `CachedNode`s that already exist when they are being constructed.
62 #[derive(Debug, Clone)]
63 pub(crate) struct CachedNode<'a> {
64 pub id: CacheId,
65 pub _phantom: PhantomData<&'a Node<'a>>,
66 }
69/// An event listener callback function.
70///
71/// It takes three parameters:
72///
73/// 1. The virtual DOM's root rendering component.
74/// 2. A capability to scheduler virtual DOM re-rendering.
75/// 3. The event that occurred.
76pub(crate) type ListenerCallback<'a> =
77 &'a (dyn Fn(&mut dyn RootRender, VdomWeak, web_sys::Event) + 'static);
79/// An event listener.
80pub struct Listener<'a> {
81 /// The type of event to listen for.
82 pub(crate) event: &'a str,
83 /// The callback to invoke when the event happens.
84 pub(crate) callback: ListenerCallback<'a>,
87/// An attribute on a DOM node, such as `id="my-thing"` or
88/// `href="https://example.com"`.
89#[derive(Clone, Debug)]
90pub struct Attribute<'a> {
91 pub(crate) name: &'a str,
92 pub(crate) value: &'a str,
95impl fmt::Debug for Listener<'_> {
96 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97 let (a, b) = self.get_callback_parts();
98 let a = a as *mut u32;
99 let b = b as *mut u32;
100 f.debug_struct("Listener")
101 .field("event", &self.event)
102 .field("callback", &(a, b))
103 .finish()
104 }
107impl<'a> Attribute<'a> {
108 /// Get this attribute's name, such as `"id"` in `<div id="my-thing" />`.
109 #[inline]
110 pub fn name(&self) -> &'a str {
111 self.name
112 }
114 /// The attribute value, such as `"my-thing"` in `<div id="my-thing" />`.
115 #[inline]
116 pub fn value(&self) -> &'a str {
117 self.value
118 }
120 /// Certain attributes are considered "volatile" and can change via user
121 /// input that we can't see when diffing against the old virtual DOM. For
122 /// these attributes, we want to always re-set the attribute on the physical
123 /// DOM node, even if the old and new virtual DOM nodes have the same value.
124 #[inline]
125 pub(crate) fn is_volatile(&self) -> bool {
126 match self.name {
127 "value" | "checked" | "selected" => true,
128 _ => false,
129 }
130 }
133impl<'a> Node<'a> {
134 /// Construct a new Node of type element with given tag name and children
135 #[inline]
136 pub(crate) fn element<Listeners, Attributes, Children>(
137 bump: &'a Bump,
138 tag_name: &'a str,
139 listeners: Listeners,
140 attributes: Attributes,
141 children: Children,
142 namespace: Option<&'a str>,
143 ) -> Node<'a>
144 where
145 Listeners: 'a + AsRef<[Listener<'a>]>,
146 Attributes: 'a + AsRef<[Attribute<'a>]>,
147 Children: 'a + AsRef<[Node<'a>]>,
148 {
149 let children: &'a Children = bump.alloc(children);
150 let children: &'a [Node<'a>] = children.as_ref();
152 let listeners: &'a Listeners = bump.alloc(listeners);
153 let listeners: &'a [Listener<'a>] = listeners.as_ref();
155 let attributes: &'a Attributes = bump.alloc(attributes);
156 let attributes: &'a [Attribute<'a>] = attributes.as_ref();
158 Node {
159 kind: NodeKind::Element(ElementNode {
160 tag_name,
161 listeners,
162 attributes,
163 children,
164 namespace,
165 }),
166 }
167 }
169 /// Construct a new text node with the given text.
170 #[inline]
171 pub(crate) fn text(text: &'a str) -> Node<'a> {
172 Node {
173 kind: NodeKind::Text(TextNode { text }),
174 }
175 }
177 /// Construct a new cached node with the given id.
178 #[inline]
179 pub(crate) fn cached(id: CacheId) -> Node<'a> {
180 Node {
181 kind: NodeKind::Cached(CachedNode {
182 id,
183 _phantom: PhantomData,
184 }),
185 }
186 }
189/// A node can become an iterator that yields the node itself once.
190///
191/// This implementation of `IntoIterator` mostly exists to improve the
192/// `typed-html` ergonomics, where the macro invokes `.into_iter()` on the child
193/// contents of a tag. By implementing `IntoIterator` here, we avoid having to
194/// do nasty shenanigans like `<p>vec![$contents]</p>` instead of plain old
195/// `<p>$contents</p>`.
196impl<'a> IntoIterator for Node<'a> {
197 type Item = Node<'a>;
198 type IntoIter = iter::Once<Self::Item>;
200 fn into_iter(self) -> Self::IntoIter {
201 iter::once(self)
202 }
205union CallbackFatPtr<'a> {
206 callback: ListenerCallback<'a>,
207 parts: (u32, u32),
210impl Listener<'_> {
211 #[inline]
212 pub(crate) fn get_callback_parts(&self) -> (u32, u32) {
213 assert_eq!(
214 mem::size_of::<ListenerCallback>(),
215 mem::size_of::<CallbackFatPtr>()
216 );
218 unsafe {
219 let fat = CallbackFatPtr {
220 callback: self.callback,
221 };
222 let (a, b) = fat.parts;
223 debug_assert!(a != 0);
224 (a, b)
225 }
226 }