nodes.rs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. //! Virtual Node Support
  2. //! --------------------
  3. //! VNodes represent lazily-constructed VDom trees that support diffing and event handlers.
  4. //!
  5. //! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
  6. use crate::{
  7. events::SyntheticEvent,
  8. innerlude::{empty_cell, Context, DomTree, ElementId, Properties, Scope, ScopeId, FC},
  9. SuspendedContext,
  10. };
  11. use bumpalo::{boxed::Box as BumpBox, Bump};
  12. use std::{
  13. cell::{Cell, RefCell},
  14. fmt::{Arguments, Debug, Formatter},
  15. marker::PhantomData,
  16. rc::Rc,
  17. };
  18. /// Tools for the base unit of the virtual dom - the VNode
  19. /// VNodes are intended to be quickly-allocated, lightweight enum values.
  20. ///
  21. /// Components will be generating a lot of these very quickly, so we want to
  22. /// limit the amount of heap allocations / overly large enum sizes.
  23. pub enum VNode<'src> {
  24. Text(VText<'src>),
  25. Element(&'src VElement<'src>),
  26. Fragment(VFragment<'src>),
  27. Component(&'src VComponent<'src>),
  28. Suspended(&'src VSuspended<'src>),
  29. Anchor(VAnchor),
  30. }
  31. impl<'src> VNode<'src> {
  32. pub fn key(&self) -> Option<&'src str> {
  33. match &self {
  34. VNode::Element(el) => el.key,
  35. VNode::Component(c) => c.key,
  36. VNode::Fragment(f) => f.key,
  37. VNode::Text(_t) => None,
  38. VNode::Suspended(_s) => None,
  39. VNode::Anchor(_f) => None,
  40. }
  41. }
  42. pub fn direct_id(&self) -> ElementId {
  43. self.try_direct_id().unwrap()
  44. }
  45. pub fn try_direct_id(&self) -> Option<ElementId> {
  46. match &self {
  47. VNode::Text(el) => el.dom_id.get(),
  48. VNode::Element(el) => el.dom_id.get(),
  49. VNode::Anchor(el) => el.dom_id.get(),
  50. VNode::Fragment(_) => None,
  51. VNode::Component(_) => None,
  52. VNode::Suspended(_) => None,
  53. }
  54. }
  55. }
  56. pub struct VAnchor {
  57. pub dom_id: Cell<Option<ElementId>>,
  58. }
  59. pub struct VText<'src> {
  60. pub text: &'src str,
  61. pub dom_id: Cell<Option<ElementId>>,
  62. pub is_static: bool,
  63. }
  64. pub struct VFragment<'src> {
  65. pub key: Option<&'src str>,
  66. pub children: &'src [VNode<'src>],
  67. pub is_static: bool,
  68. }
  69. pub struct VElement<'a> {
  70. pub tag_name: &'static str,
  71. pub namespace: Option<&'static str>,
  72. pub key: Option<&'a str>,
  73. pub dom_id: Cell<Option<ElementId>>,
  74. pub listeners: &'a [Listener<'a>],
  75. pub attributes: &'a [Attribute<'a>],
  76. pub children: &'a [VNode<'a>],
  77. }
  78. pub trait DioxusElement {
  79. const TAG_NAME: &'static str;
  80. const NAME_SPACE: Option<&'static str>;
  81. #[inline]
  82. fn tag_name(&self) -> &'static str {
  83. Self::TAG_NAME
  84. }
  85. #[inline]
  86. fn namespace(&self) -> Option<&'static str> {
  87. Self::NAME_SPACE
  88. }
  89. }
  90. /// An attribute on a DOM node, such as `id="my-thing"` or
  91. /// `href="https://example.com"`.
  92. #[derive(Clone, Debug)]
  93. pub struct Attribute<'a> {
  94. pub name: &'static str,
  95. pub value: &'a str,
  96. pub is_static: bool,
  97. pub is_volatile: bool,
  98. // Doesn't exist in the html spec, mostly used to denote "style" tags - could be for any type of group
  99. pub namespace: Option<&'static str>,
  100. }
  101. /// An event listener.
  102. /// IE onclick, onkeydown, etc
  103. pub struct Listener<'bump> {
  104. /// The type of event to listen for.
  105. pub(crate) event: &'static str,
  106. pub mounted_node: Cell<Option<ElementId>>,
  107. pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(SyntheticEvent) + 'bump>>>,
  108. }
  109. /// Virtual Components for custom user-defined components
  110. /// Only supports the functional syntax
  111. pub struct VComponent<'src> {
  112. pub key: Option<&'src str>,
  113. pub associated_scope: Cell<Option<ScopeId>>,
  114. pub(crate) caller: Rc<dyn Fn(&Scope) -> DomTree>,
  115. pub(crate) children: &'src [VNode<'src>],
  116. pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
  117. pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
  118. pub is_static: bool,
  119. pub(crate) can_memoize: bool,
  120. // Raw pointer into the bump arena for the props of the component
  121. pub(crate) raw_props: *const (),
  122. // Function pointer to the FC that was used to generate this component
  123. pub(crate) user_fc: *const (),
  124. }
  125. pub struct VSuspended<'a> {
  126. pub task_id: u64,
  127. pub dom_id: Cell<Option<ElementId>>,
  128. pub(crate) callback: RefCell<Option<&'a dyn FnOnce(SuspendedContext<'a>) -> DomTree<'a>>>,
  129. }
  130. /// This struct provides an ergonomic API to quickly build VNodes.
  131. ///
  132. /// NodeFactory is used to build VNodes in the component's memory space.
  133. /// This struct adds metadata to the final VNode about listeners, attributes, and children
  134. #[derive(Copy, Clone)]
  135. pub struct NodeFactory<'a> {
  136. pub(crate) bump: &'a Bump,
  137. }
  138. impl<'a> NodeFactory<'a> {
  139. pub fn new(bump: &'a Bump) -> NodeFactory<'a> {
  140. NodeFactory { bump }
  141. }
  142. #[inline]
  143. pub fn bump(&self) -> &'a bumpalo::Bump {
  144. &self.bump
  145. }
  146. pub fn render_directly<F>(&self, lazy_nodes: LazyNodes<'a, F>) -> DomTree<'a>
  147. where
  148. F: FnOnce(NodeFactory<'a>) -> VNode<'a>,
  149. {
  150. Some(lazy_nodes.into_vnode(NodeFactory { bump: self.bump }))
  151. }
  152. pub fn unstable_place_holder() -> VNode<'static> {
  153. VNode::Text(VText {
  154. text: "",
  155. dom_id: empty_cell(),
  156. is_static: true,
  157. })
  158. }
  159. /// Used in a place or two to make it easier to build vnodes from dummy text
  160. pub fn static_text(&self, text: &'static str) -> VNode<'a> {
  161. VNode::Text(VText {
  162. dom_id: empty_cell(),
  163. text,
  164. is_static: true,
  165. })
  166. }
  167. /// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static
  168. ///
  169. /// Text that's static may be pointer compared, making it cheaper to diff
  170. pub fn raw_text(&self, args: Arguments) -> (&'a str, bool) {
  171. match args.as_str() {
  172. Some(static_str) => (static_str, true),
  173. None => {
  174. use bumpalo::core_alloc::fmt::Write;
  175. let mut s = bumpalo::collections::String::new_in(self.bump());
  176. s.write_fmt(args).unwrap();
  177. (s.into_bump_str(), false)
  178. }
  179. }
  180. }
  181. /// Create some text that's allocated along with the other vnodes
  182. ///
  183. pub fn text(&self, args: Arguments) -> VNode<'a> {
  184. let (text, is_static) = self.raw_text(args);
  185. VNode::Text(VText {
  186. text,
  187. is_static,
  188. dom_id: empty_cell(),
  189. })
  190. }
  191. pub fn element<L, A, V>(
  192. &self,
  193. el: impl DioxusElement,
  194. listeners: L,
  195. attributes: A,
  196. children: V,
  197. key: Option<Arguments>,
  198. ) -> VNode<'a>
  199. where
  200. L: 'a + AsRef<[Listener<'a>]>,
  201. A: 'a + AsRef<[Attribute<'a>]>,
  202. V: 'a + AsRef<[VNode<'a>]>,
  203. {
  204. self.raw_element(
  205. el.tag_name(),
  206. el.namespace(),
  207. listeners,
  208. attributes,
  209. children,
  210. key,
  211. )
  212. }
  213. pub fn raw_element<L, A, V>(
  214. &self,
  215. tag: &'static str,
  216. namespace: Option<&'static str>,
  217. listeners: L,
  218. attributes: A,
  219. children: V,
  220. key: Option<Arguments>,
  221. ) -> VNode<'a>
  222. where
  223. L: 'a + AsRef<[Listener<'a>]>,
  224. A: 'a + AsRef<[Attribute<'a>]>,
  225. V: 'a + AsRef<[VNode<'a>]>,
  226. {
  227. let listeners: &'a L = self.bump().alloc(listeners);
  228. let listeners = listeners.as_ref();
  229. let attributes: &'a A = self.bump().alloc(attributes);
  230. let attributes = attributes.as_ref();
  231. let children: &'a V = self.bump().alloc(children);
  232. let children = children.as_ref();
  233. let key = key.map(|f| self.raw_text(f).0);
  234. VNode::Element(self.bump().alloc(VElement {
  235. tag_name: tag,
  236. key,
  237. namespace,
  238. listeners,
  239. attributes,
  240. children,
  241. dom_id: empty_cell(),
  242. }))
  243. }
  244. pub fn attr(
  245. &self,
  246. name: &'static str,
  247. val: Arguments,
  248. namespace: Option<&'static str>,
  249. is_volatile: bool,
  250. ) -> Attribute<'a> {
  251. let (value, is_static) = self.raw_text(val);
  252. Attribute {
  253. name,
  254. value,
  255. is_static,
  256. namespace,
  257. is_volatile,
  258. }
  259. }
  260. pub fn attr_with_alloc_val(
  261. &self,
  262. name: &'static str,
  263. val: &'a str,
  264. namespace: Option<&'static str>,
  265. is_volatile: bool,
  266. ) -> Attribute<'a> {
  267. Attribute {
  268. name,
  269. value: val,
  270. is_static: false,
  271. namespace,
  272. is_volatile,
  273. }
  274. }
  275. pub fn component<P, V>(
  276. &self,
  277. component: FC<P>,
  278. props: P,
  279. key: Option<Arguments>,
  280. children: V,
  281. ) -> VNode<'a>
  282. where
  283. P: Properties + 'a,
  284. V: 'a + AsRef<[VNode<'a>]>,
  285. {
  286. let bump = self.bump();
  287. // We don't want the fat part of the fat pointer
  288. // THe "Fat" part is provided on next-render
  289. let children: &'a V = bump.alloc(children);
  290. let children = children.as_ref();
  291. let props = bump.alloc(props);
  292. let raw_props = props as *mut P as *mut ();
  293. let user_fc = component as *const ();
  294. let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(bump.alloc_with(|| {
  295. move |other: &VComponent| {
  296. if user_fc == other.user_fc {
  297. // Safety
  298. // - We guarantee that FC<P> is the same by function pointer
  299. // - Because FC<P> is the same, then P must be the same (even with generics)
  300. // - Non-static P are autoderived to memoize as false
  301. // - This comparator is only called on a corresponding set of bumpframes
  302. let props_memoized = unsafe {
  303. let real_other: &P = &*(other.raw_props as *const _ as *const P);
  304. props.memoize(&real_other)
  305. };
  306. // It's only okay to memoize if there are no children and the props can be memoized
  307. // Implementing memoize is unsafe and done automatically with the props trait
  308. match (props_memoized, children.len() == 0) {
  309. (true, true) => true,
  310. _ => false,
  311. }
  312. } else {
  313. false
  314. }
  315. }
  316. }));
  317. // create a closure to drop the props
  318. let mut has_dropped = false;
  319. let drop_props: &mut dyn FnMut() = bump.alloc_with(|| {
  320. move || unsafe {
  321. if !has_dropped {
  322. let real_other = raw_props as *mut _ as *mut P;
  323. let b = BumpBox::from_raw(real_other);
  324. std::mem::drop(b);
  325. has_dropped = true;
  326. }
  327. }
  328. });
  329. let drop_props = unsafe { BumpBox::from_raw(drop_props) };
  330. let is_static = children.len() == 0 && P::IS_STATIC && key.is_none();
  331. let key = key.map(|f| self.raw_text(f).0);
  332. VNode::Component(bump.alloc_with(|| VComponent {
  333. user_fc,
  334. comparator,
  335. raw_props,
  336. children,
  337. caller: NodeFactory::create_component_caller(component, raw_props),
  338. is_static,
  339. drop_props: RefCell::new(Some(drop_props)),
  340. can_memoize: P::IS_STATIC,
  341. associated_scope: Cell::new(None),
  342. key,
  343. }))
  344. }
  345. pub fn create_component_caller<'g, P: 'g>(
  346. component: FC<P>,
  347. raw_props: *const (),
  348. ) -> Rc<dyn for<'r> Fn(&'r Scope) -> DomTree<'r>> {
  349. type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> DomTree<'r> + 'a>;
  350. let caller: Captured = Rc::new(move |scp: &Scope| -> DomTree {
  351. // cast back into the right lifetime
  352. let safe_props: &'_ P = unsafe { &*(raw_props as *const P) };
  353. let cx: Context<P> = Context {
  354. props: safe_props,
  355. scope: scp,
  356. };
  357. let res = component(cx);
  358. let g2 = unsafe { std::mem::transmute(res) };
  359. g2
  360. });
  361. unsafe { std::mem::transmute::<_, Captured<'static>>(caller) }
  362. }
  363. pub fn fragment_from_iter(self, node_iter: impl IntoVNodeList<'a>) -> VNode<'a> {
  364. let children = node_iter.into_vnode_list(self);
  365. // TODO
  366. // We need a dedicated path in the rsx! macro that will trigger the "you need keys" warning
  367. //
  368. // if cfg!(debug_assertions) {
  369. // if children.len() > 1 {
  370. // if children.last().unwrap().key().is_none() {
  371. // log::error!(
  372. // r#"
  373. // Warning: Each child in an array or iterator should have a unique "key" prop.
  374. // Not providing a key will lead to poor performance with lists.
  375. // See docs.rs/dioxus for more information.
  376. // ---
  377. // To help you identify where this error is coming from, we've generated a backtrace.
  378. // "#,
  379. // );
  380. // }
  381. // }
  382. // }
  383. VNode::Fragment(VFragment {
  384. children,
  385. key: None,
  386. is_static: false,
  387. })
  388. }
  389. }
  390. /// Trait implementations for use in the rsx! and html! macros.
  391. ///
  392. /// ## Details
  393. ///
  394. /// This section provides convenience methods and trait implementations for converting common structs into a format accepted
  395. /// by the macros.
  396. ///
  397. /// All dynamic content in the macros must flow in through `fragment_from_iter`. Everything else must be statically layed out.
  398. /// We pipe basically everything through `fragment_from_iter`, so we expect a very specific type:
  399. /// ```
  400. /// impl IntoIterator<Item = impl IntoVNode<'a>>
  401. /// ```
  402. ///
  403. /// As such, all node creation must go through the factory, which is only availble in the component context.
  404. /// These strict requirements make it possible to manage lifetimes and state.
  405. pub trait IntoVNode<'a> {
  406. fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
  407. }
  408. pub trait IntoVNodeList<'a> {
  409. fn into_vnode_list(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>];
  410. }
  411. impl<'a, T, V> IntoVNodeList<'a> for T
  412. where
  413. T: IntoIterator<Item = V>,
  414. V: IntoVNode<'a>,
  415. {
  416. fn into_vnode_list(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>] {
  417. let mut nodes = bumpalo::collections::Vec::new_in(cx.bump());
  418. for node in self.into_iter() {
  419. nodes.push(node.into_vnode(cx));
  420. }
  421. if nodes.len() == 0 {
  422. nodes.push(VNode::Anchor(VAnchor {
  423. dom_id: empty_cell(),
  424. }));
  425. }
  426. nodes.into_bump_slice()
  427. }
  428. }
  429. pub struct ScopeChildren<'a>(pub &'a [VNode<'a>]);
  430. impl Copy for ScopeChildren<'_> {}
  431. impl<'a> Clone for ScopeChildren<'a> {
  432. fn clone(&self) -> Self {
  433. ScopeChildren(self.0)
  434. }
  435. }
  436. impl ScopeChildren<'_> {
  437. pub unsafe fn extend_lifetime(self) -> ScopeChildren<'static> {
  438. std::mem::transmute(self)
  439. }
  440. pub unsafe fn unextend_lfetime<'a>(self) -> ScopeChildren<'a> {
  441. std::mem::transmute(self)
  442. }
  443. }
  444. impl<'a> IntoVNodeList<'a> for ScopeChildren<'a> {
  445. fn into_vnode_list(self, _: NodeFactory<'a>) -> &'a [VNode<'a>] {
  446. self.0
  447. }
  448. }
  449. // For the case where a rendered VNode is passed into the rsx! macro through curly braces
  450. impl<'a> IntoIterator for VNode<'a> {
  451. type Item = VNode<'a>;
  452. type IntoIter = std::iter::Once<Self::Item>;
  453. fn into_iter(self) -> Self::IntoIter {
  454. std::iter::once(self)
  455. }
  456. }
  457. // For the case where a rendered VNode is passed into the rsx! macro through curly braces
  458. impl<'a> IntoVNode<'a> for VNode<'a> {
  459. fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
  460. self
  461. }
  462. }
  463. /// A concrete type provider for closures that build VNode structures.
  464. ///
  465. /// This struct wraps lazy structs that build VNode trees Normally, we cannot perform a blanket implementation over
  466. /// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of IntoVNode.
  467. ///
  468. ///
  469. /// ```rust
  470. /// LazyNodes::new(|f| f.element("div", [], [], [] None))
  471. /// ```
  472. pub struct LazyNodes<'a, G>
  473. where
  474. G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
  475. {
  476. inner: G,
  477. _p: PhantomData<&'a ()>,
  478. }
  479. impl<'a, G> LazyNodes<'a, G>
  480. where
  481. G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
  482. {
  483. pub fn new(f: G) -> Self {
  484. Self {
  485. inner: f,
  486. _p: PhantomData {},
  487. }
  488. }
  489. }
  490. // Our blanket impl
  491. impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G>
  492. where
  493. G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
  494. {
  495. fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
  496. (self.inner)(cx)
  497. }
  498. }
  499. // Our blanket impl
  500. impl<'a, G> IntoIterator for LazyNodes<'a, G>
  501. where
  502. G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
  503. {
  504. type Item = Self;
  505. type IntoIter = std::iter::Once<Self::Item>;
  506. fn into_iter(self) -> Self::IntoIter {
  507. std::iter::once(self)
  508. }
  509. }
  510. // Conveniently, we also support "null" (nothing) passed in
  511. impl IntoVNode<'_> for () {
  512. fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
  513. cx.fragment_from_iter(None as Option<VNode>)
  514. }
  515. }
  516. // Conveniently, we also support "None"
  517. impl IntoVNode<'_> for Option<()> {
  518. fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
  519. cx.fragment_from_iter(None as Option<VNode>)
  520. }
  521. }
  522. impl<'a> IntoVNode<'a> for Option<VNode<'a>> {
  523. fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
  524. match self {
  525. Some(n) => n,
  526. None => cx.fragment_from_iter(None as Option<VNode>),
  527. }
  528. }
  529. }
  530. impl IntoVNode<'_> for &'static str {
  531. fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
  532. cx.static_text(self)
  533. }
  534. }
  535. impl IntoVNode<'_> for Arguments<'_> {
  536. fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
  537. cx.text(self)
  538. }
  539. }
  540. impl Debug for NodeFactory<'_> {
  541. fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  542. Ok(())
  543. }
  544. }
  545. impl Debug for VNode<'_> {
  546. fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
  547. match &self {
  548. VNode::Element(el) => {
  549. //
  550. s.debug_struct("VElement")
  551. .field("name", &el.tag_name)
  552. .field("key", &el.key)
  553. .finish()
  554. }
  555. VNode::Text(t) => write!(s, "VText {{ text: {} }}", t.text),
  556. VNode::Anchor(a) => write!(s, "VAnchor"),
  557. VNode::Fragment(frag) => write!(s, "VFragment {{ children: {:?} }}", frag.children),
  558. VNode::Suspended { .. } => write!(s, "VSuspended"),
  559. VNode::Component(comp) => write!(
  560. s,
  561. "VComponent {{ fc: {:?}, children: {:?} }}",
  562. comp.user_fc, comp.children
  563. ),
  564. }
  565. }
  566. }