maomi_dom/
lib.rs

1//! maomi: a rust framework for building pages with components
2//!
3//! This is the *DOM binding* module of the framework.
4//!
5//! ### Quick Start
6//!
7//! Pages are composed by components.
8//!
9//! To build a page, write a component which contains the page content.
10//!
11//! ```rust
12//! use wasm_bindgen::prelude::*;
13//! use maomi::prelude::*;
14//! use maomi_dom::{prelude::*, element::*};
15//!
16//! // declare a component
17//! #[component(Backend = DomBackend)]
18//! struct HelloWorld {
19//!     // a component should have a template field
20//!     template: template! {
21//!         <div>
22//!             // text in the template must be quoted
23//!             "Hello world!"
24//!         </div>
25//!     },
26//! }
27//!
28//! // the component must implement `Component` trait
29//! impl Component for HelloWorld {
30//!     fn new() -> Self {
31//!         Self {
32//!             template: Default::default(),
33//!         }
34//!     }
35//! }
36//!
37//! #[wasm_bindgen(start)]
38//! pub fn wasm_main() {
39//!     // the `<body>` is used to contain component content
40//!     let dom_backend = DomBackend::new_with_document_body().unwrap();
41//!     let backend_context = maomi::BackendContext::new(dom_backend);
42//!
43//!     // create a mount point
44//!     let mount_point = backend_context
45//!         .enter_sync(move |ctx| {
46//!             ctx.attach(|_: &mut HelloWorld| {}).unwrap()
47//!         })
48//!         .map_err(|_| "Cannot init mount point")
49//!         .unwrap();
50//!
51//!     // leak the backend context, so that event callbacks still work
52//!     std::mem::forget(mount_point);
53//!     std::mem::forget(backend_context);
54//! }
55//! ```
56//!
57
58#![warn(missing_docs)]
59
60use js_sys::JsString;
61use maomi::{
62    backend::{tree::*, *},
63    error::Error,
64};
65use wasm_bindgen::{prelude::*, JsCast, JsValue};
66
67#[cfg(all(not(feature = "prerendering"), not(feature = "prerendering-apply")))]
68macro_rules! dom_state_ty {
69    ($t:ty, $u:ty, $v:ty) => {
70        DomState<$t>
71    };
72}
73
74#[cfg(all(not(feature = "prerendering"), feature = "prerendering-apply"))]
75macro_rules! dom_state_ty {
76    ($t:ty, $u:ty, $v:ty) => {
77        DomState<$t, $v>
78    };
79}
80
81#[cfg(all(feature = "prerendering", not(feature = "prerendering-apply")))]
82macro_rules! dom_state_ty {
83    ($t:ty, $u:ty, $v:ty) => {
84        DomState<$t, $u>
85    };
86}
87
88#[cfg(all(feature = "prerendering", feature = "prerendering-apply"))]
89macro_rules! dom_state_ty {
90    ($t:ty, $u:ty, $v:ty) => {
91        DomState<$t, $u, $v>
92    };
93}
94
95pub mod base_element;
96use base_element::DomElement;
97#[cfg(feature = "prerendering")]
98use base_element::PrerenderingElement;
99#[cfg(feature = "prerendering-apply")]
100use base_element::RematchedDomElem;
101pub mod element;
102mod virtual_element;
103use virtual_element::DomVirtualElement;
104mod text_node;
105use text_node::DomTextNode;
106pub mod class_list;
107mod composing;
108pub mod dynamic_style;
109pub mod event;
110use event::DomListeners;
111pub mod custom_attr;
112
113pub use maomi_dom_macro::dom_define_attribute;
114
115/// The types that should usually be imported.
116///
117/// Usually, `use maomi_dom::prelude::*;` should be added in component files for convinience.
118pub mod prelude {
119    pub use crate::base_element::DomElementExt;
120    pub use crate::DomBackend;
121    pub use maomi_dom_macro::stylesheet;
122}
123
124thread_local! {
125    pub(crate) static WINDOW: web_sys::Window = web_sys::window().expect("Cannot init DOM backend outside web page environment");
126    pub(crate) static DOCUMENT: web_sys::Document = {
127        WINDOW.with(|window| {
128            window.document().expect("Cannot init DOM backend when document is not ready")
129        })
130    };
131}
132
133fn log_js_error(err: &JsValue) {
134    if let Some(err) = err.dyn_ref::<js_sys::Error>() {
135        log::error!("{}", err.message());
136    } else {
137        log::error!("(JavaScript Error)");
138    }
139}
140
141/// A common async runner for DOM environment
142#[inline]
143pub fn async_task(fut: impl 'static + std::future::Future<Output = ()>) {
144    wasm_bindgen_futures::spawn_local(fut);
145}
146
147#[cfg(all(not(feature = "prerendering"), not(feature = "prerendering-apply")))]
148#[derive(Debug, Clone, Copy, PartialEq)]
149pub(crate) enum DomState<T> {
150    Normal(T),
151}
152
153#[cfg(all(not(feature = "prerendering"), feature = "prerendering-apply"))]
154#[derive(Debug, Clone, Copy, PartialEq)]
155pub enum DomState<T, V> {
156    Normal(T),
157    PrerenderingApply(V),
158}
159
160#[cfg(all(feature = "prerendering", not(feature = "prerendering-apply")))]
161#[derive(Debug, Clone, Copy, PartialEq)]
162pub enum DomState<T, U> {
163    Normal(T),
164    Prerendering(U),
165}
166
167#[cfg(all(feature = "prerendering", feature = "prerendering-apply"))]
168#[derive(Debug, Clone, Copy, PartialEq)]
169enum DomState<T, U, V> {
170    Normal(T),
171    Prerendering(U),
172    PrerenderingApply(V),
173}
174
175#[derive(Debug, Default)]
176pub(crate) struct WriteHtmlState {
177    #[allow(dead_code)]
178    prev_is_text_node: bool,
179}
180
181/// A DOM backend
182pub struct DomBackend {
183    backend_stage: BackendStage,
184    tree: tree::ForestNodeRc<DomGeneralElement>,
185    #[allow(dead_code)]
186    listeners: dom_state_ty!(DomListeners, (), ()),
187}
188
189impl DomBackend {
190    /// Create a backend that rendering under the specified DOM element
191    #[inline]
192    pub fn new_with_element(dom_elem: web_sys::Element) -> Result<Self, Error> {
193        Ok(Self::wrap_root_element(dom_elem)?)
194    }
195
196    /// Create a backend that rendering under the DOM element with the `id`
197    #[inline]
198    pub fn new_with_element_id(id: &str) -> Result<Self, Error> {
199        let dom_elem = DOCUMENT
200            .with(|document| document.get_element_by_id(id))
201            .ok_or_else(|| Error::BackendError {
202                msg: format!("Cannot find the element {:?}", id),
203                err: None,
204            })?;
205        Ok(Self::wrap_root_element(dom_elem)?)
206    }
207
208    /// Create a backend that rendering under the DOM `<body>`
209    #[inline]
210    pub fn new_with_document_body() -> Result<Self, Error> {
211        let dom_elem =
212            DOCUMENT
213                .with(|document| document.body())
214                .ok_or_else(|| Error::BackendError {
215                    msg: "Cannot find the <body> element".into(),
216                    err: None,
217                })?;
218        Ok(Self::wrap_root_element(dom_elem.into())?)
219    }
220
221    fn wrap_root_element(dom_elem: web_sys::Element) -> Result<Self, Error> {
222        let listeners = DomState::Normal(event::DomListeners::new(&dom_elem));
223        let tree_root = {
224            let ret = tree::ForestNodeRc::new_forest(DomGeneralElement::Element(unsafe {
225                DomElement::new(DomState::Normal(dom_elem))
226            }));
227            let token = ret.token();
228            if let DomGeneralElement::Element(x) = &mut *ret.borrow_mut() {
229                x.init(token);
230            } else {
231                unreachable!()
232            }
233            ret
234        };
235        Ok(Self {
236            backend_stage: BackendStage::Normal,
237            tree: tree_root,
238            listeners,
239        })
240    }
241
242    /// Create a backend for prerendering
243    ///
244    /// The prerendering can generate HTML segment without DOM environment.
245    /// It can be used for server side rendering.
246    #[cfg(feature = "prerendering")]
247    #[inline]
248    pub fn prerendering() -> Self {
249        let tree_root = {
250            let ret = tree::ForestNodeRc::new_forest(DomGeneralElement::Element(unsafe {
251                DomElement::new(DomState::Prerendering(PrerenderingElement::new("maomi")))
252            }));
253            let token = ret.token();
254            if let DomGeneralElement::Element(x) = &mut *ret.borrow_mut() {
255                x.init(token);
256            } else {
257                unreachable!()
258            }
259            ret
260        };
261        Self {
262            backend_stage: BackendStage::Prerendering,
263            tree: tree_root,
264            listeners: DomState::Prerendering(()),
265        }
266    }
267
268    /// Write the prerendering result to a `Write`
269    ///
270    /// The prerendering result is an HTML segment,
271    /// which should be placed into the full HTML file as the final server response.
272    #[cfg(feature = "prerendering")]
273    #[inline]
274    pub fn write_prerendering_html(&self, w: &mut impl std::io::Write) -> std::io::Result<()> {
275        let mut state = Default::default();
276        DomGeneralElement::write_inner_html(&self.root(), w, &mut state)
277    }
278
279    /// Prepare a backend for using the prerendering result
280    ///
281    /// The prerendering result can be attached later with one of the `apply_prerendered_*` method.
282    #[cfg(feature = "prerendering-apply")]
283    #[inline]
284    pub fn new_prerendered() -> Self {
285        let tree_root = {
286            let ret = tree::ForestNodeRc::new_forest(DomGeneralElement::Element(unsafe {
287                DomElement::new(DomState::PrerenderingApply(
288                    base_element::RematchedDomElem::new(),
289                ))
290            }));
291            let token = ret.token();
292            if let DomGeneralElement::Element(x) = &mut *ret.borrow_mut() {
293                x.init(token);
294            } else {
295                unreachable!()
296            }
297            ret
298        };
299        Self {
300            backend_stage: BackendStage::PrerenderingApply,
301            tree: tree_root,
302            listeners: DomState::PrerenderingApply(()),
303        }
304    }
305
306    /// Attach the prerendering result with the specified DOM element
307    #[cfg(feature = "prerendering-apply")]
308    #[inline]
309    pub fn apply_prerendered_element(&mut self, dom_elem: web_sys::Element) -> Result<(), Error> {
310        self.apply_prerendered(dom_elem.into())
311    }
312
313    /// Attach the prerendering result with the DOM element with the `id`
314    #[cfg(feature = "prerendering-apply")]
315    #[inline]
316    pub fn apply_prerendered_element_id(&mut self, id: &str) -> Result<(), Error> {
317        let dom_elem = DOCUMENT
318            .with(|document| document.get_element_by_id(id))
319            .ok_or_else(|| Error::BackendError {
320                msg: format!("Cannot find the element {:?}", id),
321                err: None,
322            })?;
323        self.apply_prerendered(dom_elem.into())
324    }
325
326    /// Attach the prerendering result with the DOM `<body>`
327    #[cfg(feature = "prerendering-apply")]
328    #[inline]
329    pub fn apply_prerendered_document_body(&mut self) -> Result<(), Error> {
330        let dom_elem =
331            DOCUMENT
332                .with(|document| document.body())
333                .ok_or_else(|| Error::BackendError {
334                    msg: "Cannot find the <body> element".into(),
335                    err: None,
336                })?;
337        self.apply_prerendered(dom_elem.into())
338    }
339
340    #[cfg(feature = "prerendering-apply")]
341    #[inline]
342    fn apply_prerendered(&mut self, dom_elem: web_sys::Element) -> Result<(), Error> {
343        if self.backend_stage != BackendStage::PrerenderingApply {
344            panic!("The backend is not in prerendering-apply stage");
345        }
346        self.backend_stage = BackendStage::Normal;
347        self.listeners = DomState::Normal(event::DomListeners::new(&dom_elem));
348        fn rematch_dom<'a>(
349            n: &mut ForestNodeMut<'a, DomGeneralElement>,
350            next_dom_elem: Option<web_sys::Node>,
351            state: &mut WriteHtmlState,
352        ) -> Result<Option<web_sys::Node>, Error> {
353            enum ChildMatchKind {
354                Virtual(Option<web_sys::Node>),
355                NonVirtual(web_sys::Node),
356            }
357            let child_match_kind = {
358                let ge: &mut DomGeneralElement = n;
359                match ge {
360                    DomGeneralElement::Text(x) => {
361                        let mut e = next_dom_elem.ok_or(Error::BackendError {
362                            msg: "Failed to apply a prerendered node".to_string(),
363                            err: None,
364                        })?;
365                        if state.prev_is_text_node {
366                            e = e.next_sibling().ok_or(Error::BackendError {
367                                msg: "Failed to apply a prerendered node".to_string(),
368                                err: None,
369                            })?;
370                        } else {
371                            state.prev_is_text_node = true;
372                        };
373                        let ret = e.next_sibling();
374                        x.rematch_dom(e);
375                        return Ok(ret);
376                    }
377                    DomGeneralElement::Virtual(x) => {
378                        x.rematch_dom();
379                        ChildMatchKind::Virtual(next_dom_elem)
380                    }
381                    DomGeneralElement::Element(x) => {
382                        let e = next_dom_elem.ok_or(Error::BackendError {
383                            msg: "Failed to apply a prerendered node".to_string(),
384                            err: None,
385                        })?;
386                        x.rematch_dom(e.clone());
387                        state.prev_is_text_node = false;
388                        ChildMatchKind::NonVirtual(e)
389                    }
390                }
391            };
392            let mut rematch_child = |mut child_dom_elem| -> Result<_, Error> {
393                if let Some(mut child) = n.first_child_rc() {
394                    loop {
395                        let c = {
396                            let child_mut = &mut n.borrow_mut(&child);
397                            child_dom_elem = rematch_dom(child_mut, child_dom_elem, state)?;
398                            child_mut.next_sibling_rc()
399                        };
400                        match c {
401                            None => break,
402                            Some(c) => {
403                                child = c;
404                            }
405                        }
406                    }
407                }
408                Ok(child_dom_elem)
409            };
410            match child_match_kind {
411                ChildMatchKind::Virtual(x) => rematch_child(x),
412                ChildMatchKind::NonVirtual(x) => {
413                    rematch_child(x.first_child())?;
414                    state.prev_is_text_node = false;
415                    Ok(x.next_sibling())
416                }
417            }
418        }
419        let mut tree = self.tree.try_borrow_mut().ok_or(Error::BackendError {
420            msg: "Cannot apply prerendered tree while visiting".to_string(),
421            err: None,
422        })?;
423        rematch_dom(
424            &mut tree,
425            Some(dom_elem.unchecked_into()),
426            &mut Default::default(),
427        )?;
428        Ok(())
429    }
430}
431
432impl Backend for DomBackend {
433    type GeneralElement = DomGeneralElement;
434    type VirtualElement = DomVirtualElement;
435    type TextNode = DomTextNode;
436
437    #[inline]
438    fn async_task(fut: impl 'static + std::future::Future<Output = ()>) {
439        async_task(fut)
440    }
441
442    #[inline]
443    fn backend_stage(&self) -> BackendStage {
444        self.backend_stage
445    }
446
447    #[inline]
448    fn root(&self) -> ForestNode<Self::GeneralElement> {
449        self.tree.borrow()
450    }
451
452    #[inline]
453    fn root_mut(&mut self) -> ForestNodeMut<Self::GeneralElement> {
454        self.tree.borrow_mut()
455    }
456}
457
458#[doc(hidden)]
459pub enum DomGeneralElement {
460    Virtual(DomVirtualElement),
461    Text(DomTextNode),
462    Element(DomElement),
463}
464
465#[wasm_bindgen]
466extern "C" {
467    #[wasm_bindgen(js_namespace = document, js_name = createElement)]
468    fn document_create_element(n: &JsString) -> web_sys::Element;
469}
470
471impl std::fmt::Debug for DomGeneralElement {
472    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473        match self {
474            Self::Virtual(_) => write!(f, "[Virtual {:p}]", self),
475            Self::Text(x) => write!(f, "{:?}", x.text_content(),),
476            Self::Element(x) => write!(f, "{:?}", x),
477        }
478    }
479}
480
481impl DomGeneralElement {
482    fn is_prerendering(&self) -> dom_state_ty!((), (), ()) {
483        match self {
484            Self::Virtual(x) => x.is_prerendering(),
485            Self::Text(x) => x.is_prerendering(),
486            Self::Element(x) => x.is_prerendering(),
487        }
488    }
489
490    pub(crate) fn create_dom_element_by_tag_name(
491        &self,
492        _tag_name: &MaybeJsStr,
493    ) -> dom_state_ty!(web_sys::Element, PrerenderingElement, RematchedDomElem) {
494        match self.is_prerendering() {
495            #[cfg(target_arch = "wasm32")]
496            DomState::Normal(_) => DomState::Normal(document_create_element(&_tag_name.js)),
497            #[cfg(not(target_arch = "wasm32"))]
498            DomState::Normal(_) => panic!("not available in non-web environment"),
499            #[cfg(feature = "prerendering")]
500            DomState::Prerendering(_) => {
501                DomState::Prerendering(PrerenderingElement::new(_tag_name.s))
502            }
503            #[cfg(feature = "prerendering-apply")]
504            DomState::PrerenderingApply(_) => DomState::PrerenderingApply(RematchedDomElem::new()),
505        }
506    }
507
508    pub(crate) fn wrap_dom_element<'b>(
509        this: &'b mut ForestNodeMut<Self>,
510        elem: &'b dom_state_ty!(web_sys::Element, PrerenderingElement, RematchedDomElem),
511    ) -> ForestNodeRc<Self> {
512        let ret = this.new_tree(Self::Element(unsafe { DomElement::new(elem.clone()) }));
513        let token = ret.token();
514        if let Self::Element(x) = &mut *this.borrow_mut(&ret) {
515            x.init(token);
516        } else {
517            unreachable!()
518        }
519        ret
520    }
521
522    pub(crate) fn to_lazy(
523        elem: dom_state_ty!(web_sys::Element, PrerenderingElement, RematchedDomElem),
524    ) -> dom_state_ty!(web_sys::Element, (), RematchedDomElem) {
525        match elem {
526            DomState::Normal(x) => DomState::Normal(x),
527            #[cfg(feature = "prerendering")]
528            DomState::Prerendering(_) => DomState::Prerendering(()),
529            #[cfg(feature = "prerendering-apply")]
530            DomState::PrerenderingApply(x) => DomState::PrerenderingApply(x.clone()),
531        }
532    }
533
534    pub(crate) fn as_dom_element_mut<'b>(
535        this: &'b mut ForestNodeMut<Self>,
536    ) -> Option<ForestValueMut<'b, DomElement>> {
537        if let Self::Element(_) = &mut **this {
538            Some(this.map(|g| {
539                if let Self::Element(e) = g {
540                    e
541                } else {
542                    unreachable!()
543                }
544            }))
545        } else {
546            None
547        }
548    }
549
550    pub(crate) fn write_inner_html(
551        this: &ForestNode<Self>,
552        w: &mut impl std::io::Write,
553        state: &mut WriteHtmlState,
554    ) -> std::io::Result<()> {
555        match &**this {
556            Self::Text(x) => {
557                x.write_inner_html(w, state)?;
558            }
559            Self::Element(x) => {
560                x.write_inner_html(this, w, state)?;
561            }
562            Self::Virtual(_) => {
563                let mut cur = this.first_child();
564                while let Some(c) = &cur {
565                    Self::write_outer_html(&c, w, state)?;
566                    cur = c.next_sibling();
567                }
568            }
569        }
570        Ok(())
571    }
572
573    pub(crate) fn write_outer_html(
574        this: &ForestNode<Self>,
575        w: &mut impl std::io::Write,
576        state: &mut WriteHtmlState,
577    ) -> std::io::Result<()> {
578        match &**this {
579            Self::Text(x) => {
580                x.write_inner_html(w, state)?;
581            }
582            Self::Element(x) => {
583                x.write_outer_html(this, w, state)?;
584            }
585            Self::Virtual(_) => {
586                let mut cur = this.first_child();
587                while let Some(c) = &cur {
588                    Self::write_outer_html(&c, w, state)?;
589                    cur = c.next_sibling();
590                }
591            }
592        }
593        Ok(())
594    }
595
596    /// Get the inner HTML of the specified node
597    #[inline]
598    pub fn inner_html(this: &ForestNode<Self>) -> String {
599        let mut ret = Vec::new();
600        let mut state = Default::default();
601        Self::write_inner_html(this, &mut ret, &mut state).unwrap();
602        // since all str sources are valid UTF-8, this operation is safe
603        unsafe { String::from_utf8_unchecked(ret) }
604    }
605
606    /// Get the outer HTML of the specified node
607    #[inline]
608    pub fn outer_html(this: &ForestNode<Self>) -> String {
609        let mut ret = Vec::new();
610        let mut state = Default::default();
611        Self::write_outer_html(this, &mut ret, &mut state).unwrap();
612        // since all str sources are valid UTF-8, this operation is safe
613        unsafe { String::from_utf8_unchecked(ret) }
614    }
615}
616
617impl BackendGeneralElement for DomGeneralElement {
618    type BaseBackend = DomBackend;
619
620    #[inline]
621    fn as_virtual_element_mut<'b>(
622        this: &'b mut ForestNodeMut<Self>,
623    ) -> Option<
624        ForestValueMut<
625            'b,
626            <<Self as BackendGeneralElement>::BaseBackend as Backend>::VirtualElement,
627        >,
628    >
629    where
630        Self: Sized,
631    {
632        if let Self::Virtual(_) = &mut **this {
633            Some(this.map(|g| {
634                if let Self::Virtual(e) = g {
635                    e
636                } else {
637                    unreachable!()
638                }
639            }))
640        } else {
641            None
642        }
643    }
644
645    #[inline]
646    fn as_text_node_mut<'b>(
647        this: &'b mut ForestNodeMut<Self>,
648    ) -> Option<
649        ForestValueMut<'b, <<Self as BackendGeneralElement>::BaseBackend as Backend>::TextNode>,
650    >
651    where
652        Self: Sized,
653    {
654        if let Self::Text(_) = &mut **this {
655            Some(this.map(|g| {
656                if let DomGeneralElement::Text(e) = g {
657                    e
658                } else {
659                    unreachable!()
660                }
661            }))
662        } else {
663            None
664        }
665    }
666
667    #[inline]
668    fn create_virtual_element<'b>(
669        this: &'b mut ForestNodeMut<Self>,
670    ) -> Result<ForestNodeRc<<Self::BaseBackend as Backend>::GeneralElement>, Error>
671    where
672        Self: Sized,
673    {
674        let elem = DomVirtualElement::new(this);
675        let child = this.new_tree(Self::Virtual(elem));
676        Ok(child)
677    }
678
679    #[inline]
680    fn create_text_node(
681        this: &mut ForestNodeMut<Self>,
682        content: &str,
683    ) -> Result<ForestNodeRc<<Self::BaseBackend as Backend>::GeneralElement>, Error>
684    where
685        Self: Sized,
686    {
687        let elem = DomTextNode::new(this, content);
688        let child = this.new_tree(Self::Text(elem));
689        Ok(child)
690    }
691
692    #[inline]
693    fn append<'b>(
694        this: &'b mut ForestNodeMut<Self>,
695        child: &'b ForestNodeRc<
696            <<Self as BackendGeneralElement>::BaseBackend as Backend>::GeneralElement,
697        >,
698    ) where
699        Self: Sized,
700    {
701        this.append(&child);
702        if this.is_prerendering() == DomState::Normal(()) {
703            let this = this.as_ref();
704            if let Some(parent) = composing::find_nearest_dom_ancestor(this.clone()) {
705                let child = this.last_child_rc().unwrap();
706                let child = this.borrow(&child);
707                let before = composing::find_next_dom_sibling(child.clone());
708                let child_frag = composing::collect_child_frag(child);
709                if let Some(child_frag) = child_frag.dom() {
710                    parent.insert_before(child_frag, before.as_ref()).unwrap();
711                }
712            }
713        }
714    }
715
716    #[inline]
717    fn insert<'b>(
718        this: &'b mut ForestNodeMut<Self>,
719        target: &'b ForestNodeRc<
720            <<Self as BackendGeneralElement>::BaseBackend as Backend>::GeneralElement,
721        >,
722    ) where
723        Self: Sized,
724    {
725        this.insert(&target);
726        if this.is_prerendering() == DomState::Normal(()) {
727            let target = this.as_ref().borrow(&target);
728            if let Some(parent) = composing::find_nearest_dom_ancestor(target.clone()) {
729                let before = composing::find_next_dom_sibling(target.clone());
730                let child_frag = composing::collect_child_frag(target);
731                if let Some(child_frag) = child_frag.dom() {
732                    parent.insert_before(child_frag, before.as_ref()).unwrap();
733                }
734            }
735        }
736    }
737
738    #[inline]
739    fn temp_detach(
740        this: ForestNodeMut<Self>,
741    ) -> ForestNodeRc<<<Self as BackendGeneralElement>::BaseBackend as Backend>::GeneralElement>
742    where
743        Self: Sized,
744    {
745        this.detach()
746    }
747
748    #[inline]
749    fn detach(
750        this: ForestNodeMut<Self>,
751    ) -> ForestNodeRc<<<Self as BackendGeneralElement>::BaseBackend as Backend>::GeneralElement>
752    where
753        Self: Sized,
754    {
755        if this.is_prerendering() == DomState::Normal(()) {
756            let this = this.as_ref();
757            if let Some(parent) = composing::find_nearest_dom_ancestor(this.clone()) {
758                composing::remove_all_children(&parent, this);
759            }
760        }
761        let ret = this.detach();
762        ret
763    }
764}
765
766/// A combination of string and its cache in js as a `JsString`
767#[derive(Debug, Clone)]
768pub struct MaybeJsStr {
769    s: &'static str,
770    #[cfg(target_arch = "wasm32")]
771    js: js_sys::JsString,
772}
773
774impl PartialEq for MaybeJsStr {
775    fn eq(&self, other: &Self) -> bool {
776        self.s == other.s
777    }
778}
779
780impl MaybeJsStr {
781    /// Create a new string with JsString cached.
782    pub fn new_leaked(s: &'static str) -> &'static Self {
783        #[cfg(target_arch = "wasm32")]
784        let js = js_sys::JsString::from(s);
785        let this = Box::new(Self {
786            s,
787            #[cfg(target_arch = "wasm32")]
788            js,
789        });
790        Box::leak(this)
791    }
792}