yew_stdweb/virtual_dom/
mod.rs

1//! This module contains Yew's implementation of a reactive virtual DOM.
2
3#[doc(hidden)]
4pub mod key;
5#[doc(hidden)]
6pub mod vcomp;
7#[doc(hidden)]
8pub mod vlist;
9#[doc(hidden)]
10pub mod vnode;
11#[doc(hidden)]
12pub mod vtag;
13#[doc(hidden)]
14pub mod vtext;
15
16use crate::html::{AnyScope, IntoOptPropValue, NodeRef};
17use cfg_if::cfg_if;
18use indexmap::IndexMap;
19use std::{borrow::Cow, collections::HashMap, fmt, hint::unreachable_unchecked, iter, mem, rc::Rc};
20cfg_if! {
21    if #[cfg(feature = "std_web")] {
22        use crate::html::EventListener;
23        use stdweb::web::{Element, INode, Node};
24    } else if #[cfg(feature = "web_sys")] {
25        use gloo::events::EventListener;
26        use web_sys::{Element, Node};
27    }
28}
29
30#[doc(inline)]
31pub use self::key::Key;
32#[doc(inline)]
33pub use self::vcomp::{VChild, VComp};
34#[doc(inline)]
35pub use self::vlist::VList;
36#[doc(inline)]
37pub use self::vnode::VNode;
38#[doc(inline)]
39pub use self::vtag::VTag;
40#[doc(inline)]
41pub use self::vtext::VText;
42
43/// The `Listener` trait is an universal implementation of an event listener
44/// which is used to bind Rust-listener to JS-listener (DOM).
45pub trait Listener {
46    /// Returns the name of the event
47    fn kind(&self) -> &'static str;
48    /// Attaches a listener to the element.
49    fn attach(&self, element: &Element) -> EventListener;
50}
51
52impl fmt::Debug for dyn Listener {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        write!(f, "Listener {{ kind: {} }}", self.kind())
55    }
56}
57
58/// A list of event listeners.
59type Listeners = Vec<Rc<dyn Listener>>;
60
61/// Attribute value
62pub type AttrValue = Cow<'static, str>;
63
64/// Key-value tuple which makes up an item of the [`Attributes::Vec`] variant.
65#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct PositionalAttr(pub &'static str, pub Option<AttrValue>);
67impl PositionalAttr {
68    /// Create a positional attribute
69    pub fn new(key: &'static str, value: impl IntoOptPropValue<AttrValue>) -> Self {
70        Self(key, value.into_opt_prop_value())
71    }
72
73    /// Create a boolean attribute.
74    /// `present` controls whether the attribute is added
75    pub fn new_boolean(key: &'static str, present: bool) -> Self {
76        let value = if present {
77            Some(Cow::Borrowed(key))
78        } else {
79            None
80        };
81        Self::new(key, value)
82    }
83
84    /// Create a placeholder for removed attributes
85    pub fn new_placeholder(key: &'static str) -> Self {
86        Self(key, None)
87    }
88
89    fn transpose(self) -> Option<(&'static str, AttrValue)> {
90        let Self(key, value) = self;
91        value.map(|v| (key, v))
92    }
93
94    fn transposed<'a>(&'a self) -> Option<(&'static str, &'a AttrValue)> {
95        let Self(key, value) = self;
96        value.as_ref().map(|v| (*key, v))
97    }
98}
99
100/// A collection of attributes for an element
101#[derive(PartialEq, Eq, Clone, Debug)]
102pub enum Attributes {
103    /// A vector is ideal because most of the time the list will neither change
104    /// length nor key order.
105    Vec(Vec<PositionalAttr>),
106
107    /// IndexMap is used to provide runtime attribute deduplication in cases where the html! macro
108    /// was not used to guarantee it.
109    IndexMap(IndexMap<&'static str, AttrValue>),
110}
111impl Attributes {
112    /// Construct a default Attributes instance
113    pub fn new() -> Self {
114        Self::default()
115    }
116
117    /// Return iterator over attribute key-value pairs
118    pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (&'static str, &'a str)> + 'a> {
119        match self {
120            Self::Vec(v) => Box::new(
121                v.iter()
122                    .filter_map(PositionalAttr::transposed)
123                    .map(|(k, v)| (k, v.as_ref())),
124            ),
125            Self::IndexMap(m) => Box::new(m.iter().map(|(k, v)| (*k, v.as_ref()))),
126        }
127    }
128
129    /// Get a mutable reference to the underlying `IndexMap`.
130    /// If the attributes are stored in the `Vec` variant, it will be converted.
131    pub fn get_mut_index_map(&mut self) -> &mut IndexMap<&'static str, AttrValue> {
132        match self {
133            Self::IndexMap(m) => m,
134            Self::Vec(v) => {
135                *self = Self::IndexMap(
136                    mem::take(v)
137                        .into_iter()
138                        .filter_map(PositionalAttr::transpose)
139                        .collect(),
140                );
141                match self {
142                    Self::IndexMap(m) => m,
143                    // SAFETY: unreachable because we set the value to the `IndexMap` variant above.
144                    _ => unsafe { unreachable_unchecked() },
145                }
146            }
147        }
148    }
149
150    fn diff_vec<'a>(
151        new: &'a [PositionalAttr],
152        old: &[PositionalAttr],
153    ) -> Vec<Patch<&'static str, &'a str>> {
154        let mut out = Vec::new();
155        let mut new_iter = new.iter();
156        let mut old_iter = old.iter();
157
158        loop {
159            match (new_iter.next(), old_iter.next()) {
160                (
161                    Some(PositionalAttr(key, new_value)),
162                    Some(PositionalAttr(old_key, old_value)),
163                ) if key == old_key => match (new_value, old_value) {
164                    (Some(new), Some(old)) => {
165                        if new != old {
166                            out.push(Patch::Replace(*key, new.as_ref()));
167                        }
168                    }
169                    (Some(value), None) => out.push(Patch::Add(*key, value.as_ref())),
170                    (None, Some(_)) => out.push(Patch::Remove(*key)),
171                    (None, None) => {}
172                },
173                // keys don't match, we can no longer compare linearly from here on out
174                (Some(new_attr), Some(old_attr)) => {
175                    // assume that every attribute is new.
176                    let mut added = iter::once(new_attr)
177                        .chain(new_iter)
178                        .filter_map(PositionalAttr::transposed)
179                        .map(|(key, value)| (key, value.as_ref()))
180                        .collect::<HashMap<_, _>>();
181
182                    // now filter out all the attributes that aren't new
183                    for (key, old_value) in iter::once(old_attr)
184                        .chain(old_iter)
185                        .filter_map(PositionalAttr::transposed)
186                    {
187                        if let Some(new_value) = added.remove(key) {
188                            // attribute still exists but changed value
189                            if new_value != old_value.as_ref() {
190                                out.push(Patch::Replace(key, new_value));
191                            }
192                        } else {
193                            // attribute no longer exists
194                            out.push(Patch::Remove(key));
195                        }
196                    }
197
198                    // finally, we're left with the attributes that are actually new.
199                    out.extend(added.into_iter().map(|(k, v)| Patch::Add(k, v)));
200                    break;
201                }
202                // added attributes
203                (Some(attr), None) => {
204                    for PositionalAttr(key, value) in iter::once(attr).chain(new_iter) {
205                        // only add value if it has a value
206                        if let Some(value) = value {
207                            out.push(Patch::Add(*key, value));
208                        }
209                    }
210                    break;
211                }
212                // removed attributes
213                (None, Some(attr)) => {
214                    for PositionalAttr(key, value) in iter::once(attr).chain(old_iter) {
215                        // only remove the attribute if it had a value before
216                        if value.is_some() {
217                            out.push(Patch::Remove(*key));
218                        }
219                    }
220                    break;
221                }
222                (None, None) => break,
223            }
224        }
225
226        out
227    }
228
229    fn diff_index_map<'a, A, B>(
230        // this makes it possible to diff `&'a IndexMap<_, A>` and `IndexMap<_, &'a A>`.
231        mut new_iter: impl Iterator<Item = (&'static str, &'a str)>,
232        new: &IndexMap<&'static str, A>,
233        old: &IndexMap<&'static str, B>,
234    ) -> Vec<Patch<&'static str, &'a str>>
235    where
236        A: AsRef<str>,
237        B: AsRef<str>,
238    {
239        let mut out = Vec::new();
240        let mut old_iter = old.iter();
241        loop {
242            match (new_iter.next(), old_iter.next()) {
243                (Some((new_key, new_value)), Some((old_key, old_value))) => {
244                    if new_key != *old_key {
245                        break;
246                    }
247                    if new_value != old_value.as_ref() {
248                        out.push(Patch::Replace(new_key, new_value));
249                    }
250                }
251                // new attributes
252                (Some(attr), None) => {
253                    for (key, value) in iter::once(attr).chain(new_iter) {
254                        match old.get(key) {
255                            Some(old_value) => {
256                                if value != old_value.as_ref() {
257                                    out.push(Patch::Replace(key, value));
258                                }
259                            }
260                            None => out.push(Patch::Add(key, value)),
261                        }
262                    }
263                    break;
264                }
265                // removed attributes
266                (None, Some(attr)) => {
267                    for (key, _) in iter::once(attr).chain(old_iter) {
268                        if !new.contains_key(key) {
269                            out.push(Patch::Remove(*key));
270                        }
271                    }
272                    break;
273                }
274                (None, None) => break,
275            }
276        }
277
278        out
279    }
280
281    fn diff<'a>(new: &'a Self, old: &'a Self) -> Vec<Patch<&'static str, &'a str>> {
282        match (new, old) {
283            (Self::Vec(new), Self::Vec(old)) => Self::diff_vec(new, old),
284            (Self::Vec(new), Self::IndexMap(old)) => {
285                // this case is somewhat tricky because we need to return references to the values in `new`
286                // but we also want to turn `new` into a hash map for performance reasons
287                let new_iter = new
288                    .iter()
289                    .filter_map(PositionalAttr::transposed)
290                    .map(|(k, v)| (k, v.as_ref()));
291                // create a "view" over references to the actual data in `new`.
292                let new = new.iter().filter_map(PositionalAttr::transposed).collect();
293                Self::diff_index_map(new_iter, &new, old)
294            }
295            (Self::IndexMap(new), Self::Vec(old)) => {
296                let new_iter = new.iter().map(|(k, v)| (*k, v.as_ref()));
297                Self::diff_index_map(
298                    new_iter,
299                    new,
300                    &old.iter().filter_map(PositionalAttr::transposed).collect(),
301                )
302            }
303            (Self::IndexMap(new), Self::IndexMap(old)) => {
304                let new_iter = new.iter().map(|(k, v)| (*k, v.as_ref()));
305                Self::diff_index_map(new_iter, new, old)
306            }
307        }
308    }
309}
310
311impl From<Vec<PositionalAttr>> for Attributes {
312    fn from(v: Vec<PositionalAttr>) -> Self {
313        Self::Vec(v)
314    }
315}
316impl From<IndexMap<&'static str, AttrValue>> for Attributes {
317    fn from(v: IndexMap<&'static str, AttrValue>) -> Self {
318        Self::IndexMap(v)
319    }
320}
321
322impl Default for Attributes {
323    fn default() -> Self {
324        Self::Vec(Default::default())
325    }
326}
327
328/// Patch for DOM node modification.
329#[derive(Debug, PartialEq)]
330enum Patch<ID, T> {
331    Add(ID, T),
332    Replace(ID, T),
333    Remove(ID),
334}
335
336// TODO(#938): What about implementing `VDiff` for `Element`?
337// It would make it possible to include ANY element into the tree.
338// `Ace` editor embedding for example?
339
340/// This trait provides features to update a tree by calculating a difference against another tree.
341pub(crate) trait VDiff {
342    /// Remove self from parent.
343    fn detach(&mut self, parent: &Element);
344
345    /// Scoped diff apply to other tree.
346    ///
347    /// Virtual rendering for the node. It uses parent node and existing
348    /// children (virtual and DOM) to check the difference and apply patches to
349    /// the actual DOM representation.
350    ///
351    /// Parameters:
352    /// - `parent_scope`: the parent `Scope` used for passing messages to the
353    ///   parent `Component`.
354    /// - `parent`: the parent node in the DOM.
355    /// - `next_sibling`: the next sibling, used to efficiently find where to
356    ///   put the node.
357    /// - `ancestor`: the node that this node will be replacing in the DOM. This
358    ///   method will _always_ remove the `ancestor` from the `parent`.
359    ///
360    /// Returns a reference to the newly inserted element.
361    ///
362    /// ### Internal Behavior Notice:
363    ///
364    /// Note that these modify the DOM by modifying the reference that _already_
365    /// exists on the `ancestor`. If `self.reference` exists (which it
366    /// _shouldn't_) this method will panic.
367    ///
368    /// The exception to this is obviously `VRef` which simply uses the inner
369    /// `Node` directly (always removes the `Node` that exists).
370    fn apply(
371        &mut self,
372        parent_scope: &AnyScope,
373        parent: &Element,
374        next_sibling: NodeRef,
375        ancestor: Option<VNode>,
376    ) -> NodeRef;
377}
378
379#[cfg(feature = "web_sys")]
380pub(crate) fn insert_node(node: &Node, parent: &Element, next_sibling: Option<Node>) {
381    match next_sibling {
382        Some(next_sibling) => parent
383            .insert_before(&node, Some(&next_sibling))
384            .expect("failed to insert tag before next sibling"),
385        None => parent.append_child(node).expect("failed to append child"),
386    };
387}
388
389#[cfg(feature = "std_web")]
390pub(crate) fn insert_node(node: &impl INode, parent: &impl INode, next_sibling: Option<Node>) {
391    if let Some(next_sibling) = next_sibling {
392        parent
393            .insert_before(node, &next_sibling)
394            .expect("failed to insert tag before next sibling");
395    } else {
396        parent.append_child(node);
397    }
398}
399
400// stdweb lacks the `inner_html` method
401#[cfg(all(test, feature = "web_sys"))]
402mod layout_tests {
403    use super::*;
404    use crate::html::{AnyScope, Scope};
405    use crate::{Component, ComponentLink, Html, ShouldRender};
406
407    struct Comp;
408    impl Component for Comp {
409        type Message = ();
410        type Properties = ();
411
412        fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
413            unimplemented!()
414        }
415
416        fn update(&mut self, _: Self::Message) -> ShouldRender {
417            unimplemented!();
418        }
419
420        fn change(&mut self, _: Self::Properties) -> ShouldRender {
421            unimplemented!()
422        }
423
424        fn view(&self) -> Html {
425            unimplemented!()
426        }
427    }
428
429    pub(crate) struct TestLayout<'a> {
430        pub(crate) name: &'a str,
431        pub(crate) node: VNode,
432        pub(crate) expected: &'a str,
433    }
434
435    pub(crate) fn diff_layouts(layouts: Vec<TestLayout<'_>>) {
436        let document = crate::utils::document();
437        let parent_scope: AnyScope = Scope::<Comp>::new(None).into();
438        let parent_element = document.create_element("div").unwrap();
439        let parent_node: Node = parent_element.clone().into();
440        let end_node = document.create_text_node("END");
441        parent_node.append_child(&end_node).unwrap();
442        let mut empty_node: VNode = VText::new("").into();
443
444        // Tests each layout independently
445        let next_sibling = NodeRef::new(end_node.into());
446        for layout in layouts.iter() {
447            // Apply the layout
448            let mut node = layout.node.clone();
449            #[cfg(feature = "wasm_test")]
450            wasm_bindgen_test::console_log!("Independently apply layout '{}'", layout.name);
451            node.apply(&parent_scope, &parent_element, next_sibling.clone(), None);
452            assert_eq!(
453                parent_element.inner_html(),
454                format!("{}END", layout.expected),
455                "Independent apply failed for layout '{}'",
456                layout.name,
457            );
458
459            // Diff with no changes
460            let mut node_clone = layout.node.clone();
461            #[cfg(feature = "wasm_test")]
462            wasm_bindgen_test::console_log!("Independently reapply layout '{}'", layout.name);
463            node_clone.apply(
464                &parent_scope,
465                &parent_element,
466                next_sibling.clone(),
467                Some(node),
468            );
469            assert_eq!(
470                parent_element.inner_html(),
471                format!("{}END", layout.expected),
472                "Independent reapply failed for layout '{}'",
473                layout.name,
474            );
475
476            // Detach
477            empty_node.clone().apply(
478                &parent_scope,
479                &parent_element,
480                next_sibling.clone(),
481                Some(node_clone),
482            );
483            assert_eq!(
484                parent_element.inner_html(),
485                "END",
486                "Independent detach failed for layout '{}'",
487                layout.name,
488            );
489        }
490
491        // Sequentially apply each layout
492        let mut ancestor: Option<VNode> = None;
493        for layout in layouts.iter() {
494            let mut next_node = layout.node.clone();
495            #[cfg(feature = "wasm_test")]
496            wasm_bindgen_test::console_log!("Sequentially apply layout '{}'", layout.name);
497            next_node.apply(
498                &parent_scope,
499                &parent_element,
500                next_sibling.clone(),
501                ancestor,
502            );
503            assert_eq!(
504                parent_element.inner_html(),
505                format!("{}END", layout.expected),
506                "Sequential apply failed for layout '{}'",
507                layout.name,
508            );
509            ancestor = Some(next_node);
510        }
511
512        // Sequentially detach each layout
513        for layout in layouts.into_iter().rev() {
514            let mut next_node = layout.node.clone();
515            #[cfg(feature = "wasm_test")]
516            wasm_bindgen_test::console_log!("Sequentially detach layout '{}'", layout.name);
517            next_node.apply(
518                &parent_scope,
519                &parent_element,
520                next_sibling.clone(),
521                ancestor,
522            );
523            assert_eq!(
524                parent_element.inner_html(),
525                format!("{}END", layout.expected),
526                "Sequential detach failed for layout '{}'",
527                layout.name,
528            );
529            ancestor = Some(next_node);
530        }
531
532        // Detach last layout
533        empty_node.apply(&parent_scope, &parent_element, next_sibling, ancestor);
534        assert_eq!(
535            parent_element.inner_html(),
536            "END",
537            "Failed to detach last layout"
538        );
539    }
540}
541
542#[cfg(all(test, feature = "web_sys", feature = "wasm_bench"))]
543mod benchmarks {
544    use super::{Attributes, PositionalAttr};
545    use std::borrow::Cow;
546    use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
547
548    wasm_bindgen_test_configure!(run_in_browser);
549
550    fn create_pos_attrs() -> Vec<PositionalAttr> {
551        vec![
552            PositionalAttr::new("oh", Cow::Borrowed("danny")),
553            PositionalAttr::new("boy", Cow::Borrowed("the")),
554            PositionalAttr::new("pipes", Cow::Borrowed("the")),
555            PositionalAttr::new("are", Cow::Borrowed("calling")),
556            PositionalAttr::new("from", Cow::Borrowed("glen")),
557            PositionalAttr::new("to", Cow::Borrowed("glen")),
558            PositionalAttr::new("and", Cow::Borrowed("down")),
559            PositionalAttr::new("the", Cow::Borrowed("mountain")),
560            PositionalAttr::new("side", Cow::Borrowed("")),
561        ]
562    }
563
564    fn run_benchmarks(name: &str, new: Vec<PositionalAttr>, old: Vec<PositionalAttr>) {
565        let new_vec = Attributes::from(new);
566        let old_vec = Attributes::from(old);
567
568        let mut new_map = new_vec.clone();
569        let _ = new_map.get_mut_index_map();
570        let mut old_map = old_vec.clone();
571        let _ = old_map.get_mut_index_map();
572
573        const TIME_LIMIT: f64 = 2.0;
574
575        let vv = easybench_wasm::bench_env_limit(TIME_LIMIT, (&new_vec, &old_vec), |(new, old)| {
576            format!("{:?}", Attributes::diff(&new, &old))
577        });
578        let mm = easybench_wasm::bench_env_limit(TIME_LIMIT, (&new_map, &old_map), |(new, old)| {
579            format!("{:?}", Attributes::diff(&new, &old))
580        });
581
582        let vm = easybench_wasm::bench_env_limit(TIME_LIMIT, (&new_vec, &old_map), |(new, old)| {
583            format!("{:?}", Attributes::diff(&new, &old))
584        });
585        let mv = easybench_wasm::bench_env_limit(TIME_LIMIT, (&new_map, &old_vec), |(new, old)| {
586            format!("{:?}", Attributes::diff(&new, &old))
587        });
588
589        wasm_bindgen_test::console_log!(
590            "{}:\n\tvec-vec: {}\n\tmap-map: {}\n\tvec-map: {}\n\tmap-vec: {}",
591            name,
592            vv,
593            mm,
594            vm,
595            mv
596        );
597    }
598
599    #[wasm_bindgen_test]
600    fn bench_diff_attributes_equal() {
601        let old = create_pos_attrs();
602        let new = old.clone();
603
604        run_benchmarks("equal", new, old);
605    }
606
607    #[wasm_bindgen_test]
608    fn bench_diff_attributes_length_end() {
609        let old = create_pos_attrs();
610        let mut new = old.clone();
611        new.push(PositionalAttr::new("hidden", Cow::Borrowed("hidden")));
612
613        run_benchmarks("added to end", new.clone(), old.clone());
614        run_benchmarks("removed from end", old, new);
615    }
616    #[wasm_bindgen_test]
617    fn bench_diff_attributes_length_start() {
618        let old = create_pos_attrs();
619        let mut new = old.clone();
620        new.insert(0, PositionalAttr::new("hidden", Cow::Borrowed("hidden")));
621
622        run_benchmarks("added to start", new.clone(), old.clone());
623        run_benchmarks("removed from start", old, new);
624    }
625
626    #[wasm_bindgen_test]
627    fn bench_diff_attributes_reorder() {
628        let old = create_pos_attrs();
629        let new = old.clone().into_iter().rev().collect();
630
631        run_benchmarks("reordered", new, old);
632    }
633
634    #[wasm_bindgen_test]
635    fn bench_diff_attributes_change_first() {
636        let old = create_pos_attrs();
637        let mut new = old.clone();
638        new[0].1 = Some(Cow::Borrowed("changed"));
639
640        run_benchmarks("changed first", new, old);
641    }
642
643    #[wasm_bindgen_test]
644    fn bench_diff_attributes_change_middle() {
645        let old = create_pos_attrs();
646        let mut new = old.clone();
647        new[old.len() / 2].1 = Some(Cow::Borrowed("changed"));
648
649        run_benchmarks("changed middle", new, old);
650    }
651
652    #[wasm_bindgen_test]
653    fn bench_diff_attributes_change_last() {
654        let old = create_pos_attrs();
655        let mut new = old.clone();
656        new[old.len() - 1].1 = Some(Cow::Borrowed("changed"));
657
658        run_benchmarks("changed last", new, old);
659    }
660}