1#![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
115pub 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#[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
181pub 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 unsafe { String::from_utf8_unchecked(ret) }
604 }
605
606 #[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 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#[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 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}