sauron_core/dom/
program.rs

1use std::{
2    any::TypeId,
3    cell::{Ref, RefCell, RefMut},
4    collections::{hash_map::DefaultHasher, VecDeque},
5    hash::{Hash, Hasher},
6    mem::ManuallyDrop,
7    rc::Rc,
8    rc::Weak,
9};
10
11use wasm_bindgen::{JsCast, JsValue};
12
13use crate::{
14    dom::{
15        document, dom_patch, now, program::app_context::WeakContext, util::body,
16        AnimationFrameHandle, Application, DomNode, DomPatch, IdleCallbackHandle, IdleDeadline,
17        Measurements, SkipDiff, SkipPath,
18    },
19    html::{self, attributes::class, text},
20    vdom::{self, diff, diff_recursive, Patch},
21};
22
23#[cfg(feature = "with-raf")]
24use crate::dom::request_animation_frame;
25
26#[cfg(feature = "with-ric")]
27use crate::dom::request_idle_callback;
28
29thread_local! {
30    static CANCEL_CNT: RefCell<i32> = RefCell::new(0);
31}
32
33thread_local! {
34    static UPDATE_CNT: RefCell<i32> = RefCell::new(0);
35}
36
37mod app_context;
38use self::app_context::AppContext;
39
40mod mount_procedure;
41pub use self::mount_procedure::{MountAction, MountProcedure, MountTarget};
42
43/// Program handle the lifecycle of the APP
44pub struct Program<APP>
45where
46    APP: Application,
47{
48    pub(crate) app_context: AppContext<APP>,
49
50    /// the first element of the app view, where the patch is generated is relative to
51    pub root_node: Rc<RefCell<Option<DomNode>>>,
52
53    /// the actual DOM element where the APP is mounted to.
54    pub(crate) mount_node: Rc<RefCell<Option<DomNode>>>,
55
56    /// Pending patches that hasn't been applied to the DOM yet
57    /// for optimization purposes to avoid sluggishness of the app, when a patch
58    /// can not be run in 1 execution due to limited remaining time deadline
59    /// it will be put into the pending patches to be executed on the next run.
60    pub(crate) pending_patches: Rc<RefCell<VecDeque<DomPatch>>>,
61
62    /// store the Closure used in request_idle_callback calls
63    pub(crate) idle_callback_handles: Rc<RefCell<Vec<IdleCallbackHandle>>>,
64    /// store the Closure used in request_animation_frame calls
65    pub(crate) animation_frame_handles: Rc<RefCell<Vec<AnimationFrameHandle>>>,
66
67    /// keep track of the time when the dom is last updated
68    pub(crate) last_update: Rc<RefCell<Option<f64>>>,
69}
70
71pub struct WeakProgram<APP>
72where
73    APP: Application,
74{
75    pub(crate) app_context: WeakContext<APP>,
76    pub(crate) root_node: Weak<RefCell<Option<DomNode>>>,
77    mount_node: Weak<RefCell<Option<DomNode>>>,
78    pending_patches: Weak<RefCell<VecDeque<DomPatch>>>,
79    idle_callback_handles: Weak<RefCell<Vec<IdleCallbackHandle>>>,
80    animation_frame_handles: Weak<RefCell<Vec<AnimationFrameHandle>>>,
81    last_update: Weak<RefCell<Option<f64>>>,
82}
83
84impl<APP> WeakProgram<APP>
85where
86    APP: Application,
87{
88    ///
89    pub fn upgrade(&self) -> Option<Program<APP>> {
90        let app_context = self.app_context.upgrade()?;
91        let root_node = self.root_node.upgrade()?;
92        let mount_node = self.mount_node.upgrade()?;
93        let pending_patches = self.pending_patches.upgrade()?;
94        let idle_callback_handles = self.idle_callback_handles.upgrade()?;
95        let animation_frame_handles = self.animation_frame_handles.upgrade()?;
96        let last_update = self.last_update.upgrade()?;
97        Some(Program {
98            app_context,
99            root_node,
100            mount_node,
101            pending_patches,
102            idle_callback_handles,
103            animation_frame_handles,
104            last_update,
105        })
106    }
107}
108
109impl<APP> Clone for WeakProgram<APP>
110where
111    APP: Application,
112{
113    fn clone(&self) -> Self {
114        WeakProgram {
115            app_context: self.app_context.clone(),
116            root_node: Weak::clone(&self.root_node),
117            mount_node: Weak::clone(&self.mount_node),
118            pending_patches: Weak::clone(&self.pending_patches),
119            idle_callback_handles: Weak::clone(&self.idle_callback_handles),
120            animation_frame_handles: Weak::clone(&self.animation_frame_handles),
121            last_update: Weak::clone(&self.last_update),
122        }
123    }
124}
125
126impl<APP> Program<APP>
127where
128    APP: Application,
129{
130    ///
131    pub fn downgrade(&self) -> WeakProgram<APP> {
132        WeakProgram {
133            app_context: AppContext::downgrade(&self.app_context),
134            root_node: Rc::downgrade(&self.root_node),
135            mount_node: Rc::downgrade(&self.mount_node),
136            pending_patches: Rc::downgrade(&self.pending_patches),
137            idle_callback_handles: Rc::downgrade(&self.idle_callback_handles),
138            animation_frame_handles: Rc::downgrade(&self.animation_frame_handles),
139            last_update: Rc::downgrade(&self.last_update),
140        }
141    }
142}
143
144impl<APP> Clone for Program<APP>
145where
146    APP: Application,
147{
148    fn clone(&self) -> Self {
149        Program {
150            app_context: self.app_context.clone(),
151            root_node: Rc::clone(&self.root_node),
152            mount_node: Rc::clone(&self.mount_node),
153            pending_patches: Rc::clone(&self.pending_patches),
154            idle_callback_handles: Rc::clone(&self.idle_callback_handles),
155            animation_frame_handles: Rc::clone(&self.animation_frame_handles),
156            last_update: Rc::clone(&self.last_update),
157        }
158    }
159}
160
161impl<APP> Program<APP>
162where
163    APP: Application,
164{
165    /// get a reference to the APP
166    pub fn app(&self) -> Ref<'_, APP> {
167        self.app_context.app.borrow()
168    }
169
170    /// get a mutable reference to the APP
171    pub fn app_mut(&self) -> RefMut<'_, APP> {
172        self.app_context.app.borrow_mut()
173    }
174}
175
176impl<APP> Program<APP>
177where
178    APP: Application,
179{
180    /// Create an Rc wrapped instance of program, initializing DomUpdater with the initial view
181    /// and root node, but doesn't mount it yet.
182    pub fn new(app: APP) -> Self {
183        Self::from_rc_app(Rc::new(RefCell::new(app)))
184    }
185
186    /// create a program from Rc<RefCell<APP>>
187    pub fn from_rc_app(app: Rc<RefCell<APP>>) -> Self {
188        let app_view = app.borrow().view();
189        Program {
190            app_context: AppContext {
191                app,
192                current_vdom: Rc::new(RefCell::new(app_view)),
193                pending_msgs: Rc::new(RefCell::new(VecDeque::new())),
194                pending_dispatches: Rc::new(RefCell::new(VecDeque::new())),
195            },
196            root_node: Rc::new(RefCell::new(None)),
197            mount_node: Rc::new(RefCell::new(None)),
198            pending_patches: Rc::new(RefCell::new(VecDeque::new())),
199            idle_callback_handles: Rc::new(RefCell::new(vec![])),
200            animation_frame_handles: Rc::new(RefCell::new(vec![])),
201            last_update: Rc::new(RefCell::new(None)),
202        }
203    }
204
205    /// executed after the program has been mounted
206    fn after_mounted(&mut self) {
207        // call the init of the component
208        let init_cmd = self.app_context.init_app();
209
210        // this call may or may not trigger dispatch
211        // as the initi app of Application
212        // may just return Cmd::none which doesn't trigger
213        // dispatching / redraw
214        init_cmd.emit(self.clone());
215
216        // inject the app's dynamic style after the emitting the init function and it's effects
217        self.inject_dynamic_style();
218
219        // first dispatch call to ensure the template is patched with the
220        // new app real view
221        //self.dispatch_multiple([]);
222    }
223
224    fn app_hash() -> u64 {
225        let type_id = TypeId::of::<APP>();
226        let mut hasher = DefaultHasher::new();
227        type_id.hash(&mut hasher);
228        hasher.finish()
229    }
230
231    fn inject_stylesheet(&mut self) {
232        let static_style = self.app_context.static_style();
233        if !static_style.is_empty() {
234            let class_names = format!("static {}", Self::app_hash());
235            self.inject_style(class_names, &static_style);
236        }
237    }
238
239    fn inject_dynamic_style(&mut self) {
240        let dynamic_style = self.app_context.dynamic_style();
241        if !dynamic_style.is_empty() {
242            let class_names = format!("dynamic {}", Self::app_hash());
243            self.inject_style(class_names, &dynamic_style);
244        }
245    }
246
247    ///  Instantiage an app and append the view to the root_node
248    /// # Example
249    /// ```rust,ignore
250    /// # use sauron::prelude::*;
251    /// # use sauron::document;
252    /// struct App{}
253    /// # impl Application<()> for App{
254    /// #     fn view(&self) -> Node<()>{
255    /// #         text("hello")
256    /// #     }
257    /// #     fn update(&mut self, _: ()) -> Cmd<Self, ()> {
258    /// #         Cmd::none()
259    /// #     }
260    /// # }
261    /// let mount = document().query_selector("#app").ok().flatten().unwrap();
262    /// Program::append_to_mount(App{}, &mount);
263    /// ```
264    pub fn append_to_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
265        let mut program = Self::new(app);
266        program.mount(mount_node, MountProcedure::append());
267        ManuallyDrop::new(program)
268    }
269
270    /// Creates an Rc wrapped instance of Program and replace the root_node with the app view
271    /// # Example
272    /// ```rust,ignore
273    /// # use sauron::prelude::*;
274    /// # use sauron::document;
275    /// struct App{}
276    /// # impl Application<()> for App{
277    /// #     fn view(&self) -> Node<()>{
278    /// #         text("hello")
279    /// #     }
280    /// #     fn update(&mut self, _: ()) -> Cmd<Self, ()> {
281    /// #         Cmd::none()
282    /// #     }
283    /// # }
284    /// let mount = document().query_selector(".container").ok().flatten().unwrap();
285    /// Program::replace_mount(App{}, &mount);
286    /// ```
287    pub fn replace_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
288        let mut program = Self::new(app);
289        program.mount(mount_node, MountProcedure::replace());
290        ManuallyDrop::new(program)
291    }
292
293    /// clear the existing children of the mount before mounting the app
294    pub fn clear_append_to_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
295        let mut program = Self::new(app);
296        program.mount(mount_node, MountProcedure::clear_append());
297        ManuallyDrop::new(program)
298    }
299
300    /// clear the existing children of the mount before mounting the app
301    pub fn clear_mount(app: APP, mount_node: &web_sys::Node) -> ManuallyDrop<Self> {
302        Self::clear_append_to_mount(app, mount_node)
303    }
304
305    /// clear the existing children of the document body before mounting the app
306    pub fn clear_mount_to_body(app: APP) -> ManuallyDrop<Self> {
307        Self::clear_append_to_mount(app, &body())
308    }
309
310    /// Instantiate the app and then append it to the document body
311    /// # Example
312    /// ```rust,ignore
313    /// # use sauron::prelude::*;
314    /// # use sauron::document;
315    /// struct App{}
316    /// # impl Application<()> for App{
317    /// #     fn view(&self) -> Node<()>{
318    /// #         text("hello")
319    /// #     }
320    /// #     fn update(&mut self, _: ()) -> Cmd<Self, ()> {
321    /// #         Cmd::none()
322    /// #     }
323    /// # }
324    /// Program::mount_to_body(App{});
325    /// ```
326    pub fn mount_to_body(app: APP) -> ManuallyDrop<Self> {
327        Self::append_to_mount(app, &body())
328    }
329
330    /// executed right before the app is mounted to the dom
331    pub fn pre_mount(&mut self) {
332        self.inject_stylesheet();
333    }
334
335    #[allow(unused)]
336    /// create initial dom node generated
337    /// from template and patched by the difference of vdom_template and current app view.
338    pub(crate) fn create_initial_view(&self) -> DomNode {
339        let current_view = self.app_context.current_vdom();
340        let real_view = current_view.unwrap_template_ref();
341        self.create_dom_node(real_view)
342    }
343
344    /// each element and it's descendant in the vdom is created into
345    /// an actual DOM node.
346    pub fn mount(&mut self, mount_node: &web_sys::Node, mount_procedure: MountProcedure) {
347        let mount_node = DomNode::from(mount_node.clone());
348        *self.mount_node.borrow_mut() = Some(mount_node);
349        self.pre_mount();
350
351        let created_node = self.create_initial_view();
352
353        let mount_node: DomNode = match mount_procedure.target {
354            MountTarget::MountNode => self
355                .mount_node
356                .borrow()
357                .as_ref()
358                .expect("mount node")
359                .clone(),
360            MountTarget::ShadowRoot => {
361                let mount_element: web_sys::Element = self
362                    .mount_node
363                    .borrow()
364                    .as_ref()
365                    .expect("mount node")
366                    .as_element();
367
368                mount_element
369                    .attach_shadow(&web_sys::ShadowRootInit::new(web_sys::ShadowRootMode::Open))
370                    .expect("unable to attached shadow");
371                let mount_shadow = mount_element.shadow_root().expect("must have a shadow");
372                let shadow_node: web_sys::Node = mount_shadow.unchecked_into();
373
374                *self.mount_node.borrow_mut() = Some(DomNode::from(shadow_node));
375                self.mount_node
376                    .borrow()
377                    .as_ref()
378                    .expect("mount_node")
379                    .clone()
380            }
381        };
382
383        match mount_procedure.action {
384            MountAction::Append => {
385                mount_node.append_children(vec![created_node.clone()]);
386            }
387            MountAction::ClearAppend => {
388                mount_node.clear_children();
389                mount_node.append_children(vec![created_node.clone()]);
390            }
391            MountAction::Replace => {
392                mount_node.replace_node(created_node.clone());
393            }
394        }
395        *self.root_node.borrow_mut() = Some(created_node);
396        self.after_mounted();
397    }
398
399    #[cfg(feature = "with-ric")]
400    fn dispatch_pending_msgs_with_ric(&mut self) -> Result<(), JsValue> {
401        let program = Program::downgrade(&self);
402        let handle = request_idle_callback(move |deadline| {
403            let mut program = program.upgrade().expect("must upgrade");
404            program
405                .dispatch_pending_msgs(Some(deadline))
406                .expect("must execute")
407        })
408        .expect("must execute");
409        self.idle_callback_handles.borrow_mut().push(handle);
410        Ok(())
411    }
412
413    /// executes pending msgs by calling the app update method with the msgs
414    /// as parameters.
415    /// If there is no deadline specified all the pending messages are executed
416    fn dispatch_pending_msgs(&mut self, deadline: Option<IdleDeadline>) -> Result<(), JsValue> {
417        if !self.app_context.has_pending_msgs() {
418            return Ok(());
419        }
420        let mut did_complete = true;
421        while self.app_context.dispatch_pending_msg() {
422            // break only if a deadline is supplied
423            if let Some(deadline) = &deadline {
424                if deadline.did_timeout() {
425                    did_complete = false;
426                    break;
427                }
428            }
429        }
430        if !did_complete {
431            log::info!("did not complete pending msgs in time.. dispatching the rest");
432            #[cfg(feature = "with-ric")]
433            self.dispatch_pending_msgs_with_ric()
434                .expect("must complete");
435        }
436        Ok(())
437    }
438
439    /// execute DOM changes in order to reflect the APP's view into the browser representation
440    pub fn update_dom(&mut self) -> Result<(), JsValue> {
441        let t1 = now();
442        //#[cfg(all(feature = "with-measure", feature = "with-debug"))]
443        if let Some(last_update) = self.last_update.borrow().as_ref() {
444            let frame_time = (1000_f64 / 60_f64).floor(); // 1s in 60 frames
445            let time_delta = t1 - last_update;
446            let remaining = frame_time - time_delta;
447            if time_delta < frame_time {
448                log::warn!("update is {remaining} too soon!... time_delta: {time_delta}, frame_time: {frame_time}");
449                let mut program = self.clone();
450                //#[cfg(feature = "with-debounce")]
451                crate::dom::request_timeout_callback(
452                    move || {
453                        program.update_dom().unwrap();
454                    },
455                    remaining.round() as i32,
456                )
457                .unwrap();
458                log::info!("update is cancelled..");
459                CANCEL_CNT.with_borrow_mut(|c| *c += 1);
460                return Ok(());
461            }
462        }
463        log::info!("Doing and update...");
464        UPDATE_CNT.with_borrow_mut(|c| *c += 1);
465        log::info!(
466            "ratio(cancelled/update): {}/{}",
467            CANCEL_CNT.with_borrow(|c| *c),
468            UPDATE_CNT.with_borrow(|c| *c)
469        );
470        // a new view is created due to the app update
471        let view = self.app_context.view();
472        let t2 = now();
473
474        let node_count = view.node_count();
475        let skip_diff = view.skip_diff();
476
477        let dom_patches = if let Some(skip_diff) = skip_diff {
478            let current_vdom = self.app_context.current_vdom();
479            let real_current_vdom = current_vdom.unwrap_template_ref();
480            let real_view = view.unwrap_template_ref();
481            let patches =
482                self.create_patches_with_skip_diff(real_current_vdom, real_view, &skip_diff);
483            #[cfg(all(feature = "with-debug", feature = "log-patches"))]
484            {
485                log::info!("There are {} patches", patches.len());
486                log::info!("patches: {patches:#?}");
487            }
488            self.convert_patches(
489                self.root_node
490                    .borrow()
491                    .as_ref()
492                    .expect("must have a root node"),
493                &patches,
494            )
495            .expect("must convert patches")
496        } else {
497            let current_vdom = self.app_context.current_vdom();
498            create_dom_patch(
499                &self.root_node,
500                &current_vdom,
501                &view,
502                self.create_ev_callback(),
503            )
504        };
505
506        let total_patches = dom_patches.len();
507
508        // update the last DOM node tree with this new view
509        self.queue_dom_patches(dom_patches).expect("must not error");
510        // set the current dom
511        self.app_context.set_current_dom(view);
512        let t3 = now();
513
514        let strong_count = self.app_context.strong_count();
515        let weak_count = self.app_context.weak_count();
516        let measurements = Measurements {
517            node_count,
518            build_view_took: t2 - t1,
519            total_patches,
520            dom_update_took: t3 - t2,
521            total_time: t3 - t1,
522            strong_count,
523            weak_count,
524        };
525
526        if measurements.total_time > 16.0 {
527            #[cfg(all(feature = "with-measure", feature = "with-debug"))]
528            {
529                log::warn!("dispatch took {}ms", measurements.total_time.round());
530            }
531        }
532
533        // tell the app about the performance measurement and only if there was patches applied
534        #[cfg(feature = "with-measure")]
535        self.app_context.measurements(measurements);
536
537        *self.last_update.borrow_mut() = Some(t3);
538        Ok(())
539    }
540
541    /// patch the DOM to reflect the App's view
542    ///
543    /// Note: This is in another function so as to allow tests to use this shared code
544    pub fn queue_dom_patches(&mut self, dom_patches: Vec<DomPatch>) -> Result<(), JsValue> {
545        self.pending_patches.borrow_mut().extend(dom_patches);
546
547        #[cfg(feature = "with-raf")]
548        self.apply_pending_patches_with_raf().expect("raf");
549
550        #[cfg(not(feature = "with-raf"))]
551        self.apply_pending_patches().expect("raf");
552
553        Ok(())
554    }
555
556    pub(crate) fn create_patches_with_skip_diff<'a>(
557        &self,
558        old_vdom: &'a vdom::Node<APP::MSG>,
559        new_vdom: &'a vdom::Node<APP::MSG>,
560        skip_diff: &SkipDiff,
561    ) -> Vec<Patch<'a, APP::MSG>> {
562        use crate::vdom::TreePath;
563        assert!(!old_vdom.is_template(), "old vdom should not be a template");
564        assert!(!new_vdom.is_template(), "new vdom should not be a template");
565        diff_recursive(
566            old_vdom,
567            new_vdom,
568            &SkipPath::new(TreePath::root(), skip_diff.clone()),
569        )
570    }
571
572    #[cfg(feature = "with-raf")]
573    fn apply_pending_patches_with_raf(&mut self) -> Result<(), JsValue> {
574        let program = Program::downgrade(&self);
575        let handle = request_animation_frame(move || {
576            let mut program = program.upgrade().expect("must upgrade");
577            program.apply_pending_patches().expect("must not error");
578        })
579        .expect("must execute");
580        self.animation_frame_handles.borrow_mut().push(handle);
581        Ok(())
582    }
583
584    /// apply the pending patches into the DOM
585    fn apply_pending_patches(&mut self) -> Result<(), JsValue> {
586        if self.pending_patches.borrow().is_empty() {
587            return Ok(());
588        }
589        let dom_patches: Vec<DomPatch> = self.pending_patches.borrow_mut().drain(..).collect();
590        dom_patch::apply_dom_patches(
591            Rc::clone(&self.root_node),
592            Rc::clone(&self.mount_node),
593            dom_patches,
594        )?;
595
596        Ok(())
597    }
598
599    #[cfg(feature = "with-ric")]
600    fn dispatch_inner_with_ric(&self) {
601        let program = Program::downgrade(&self);
602        let handle = request_idle_callback(move |deadline| {
603            if let Some(mut program) = program.upgrade() {
604                program.dispatch_inner(Some(deadline));
605            } else {
606                log::warn!("unable to upgrade program.. maybe try again next time..");
607            }
608        })
609        .expect("must execute");
610        self.idle_callback_handles.borrow_mut().push(handle);
611    }
612
613    #[allow(unused)]
614    #[cfg(feature = "with-raf")]
615    fn dispatch_inner_with_raf(&self) {
616        let program = Program::downgrade(&self);
617        let handle = request_animation_frame(move || {
618            let mut program = program.upgrade().expect("must upgrade");
619            program.dispatch_inner(None);
620        })
621        .expect("must execute");
622        self.animation_frame_handles.borrow_mut().push(handle);
623    }
624
625    fn dispatch_inner_with_priority_ric(&self) {
626        #[cfg(feature = "with-ric")]
627        self.dispatch_inner_with_ric();
628        #[cfg(not(feature = "with-ric"))]
629        {
630            #[cfg(feature = "with-raf")]
631            self.dispatch_inner_with_raf();
632
633            #[cfg(not(feature = "with-raf"))]
634            {
635                let program = Program::downgrade(self);
636                wasm_bindgen_futures::spawn_local(async move {
637                    if let Some(mut program) = program.upgrade() {
638                        program.dispatch_inner(None);
639                    } else {
640                        log::warn!(
641                            "unable to upgrade program here, in dispatch_inner_with_priority_ric"
642                        );
643                    }
644                })
645            }
646        }
647    }
648
649    /// This is called when an event is triggered in the html DOM.
650    /// The sequence of things happening here:
651    /// - The app component update is executed.
652    /// - The returned Cmd from the component update is then emitted.
653    /// - The view is reconstructed with the new state of the app.
654    /// - The dom is updated with the newly reconstructed view.
655    fn dispatch_inner(&mut self, deadline: Option<IdleDeadline>) {
656        self.dispatch_pending_msgs(deadline)
657            .expect("must dispatch msgs");
658        // ensure that all pending msgs are all dispatched already
659        #[cfg(feature = "ensure-check")]
660        if self.app_context.has_pending_msgs() {
661            log::info!(
662                "There are still: {} pending msgs",
663                self.app_context.pending_msgs_count()
664            );
665            self.dispatch_pending_msgs(None)
666                .expect("must dispatch all pending msgs");
667        }
668        #[cfg(feature = "ensure-check")]
669        if self.app_context.has_pending_msgs() {
670            panic!("Can not proceed until previous pending msgs are dispatched..");
671        }
672
673        let cmd = self.app_context.batch_pending_cmds();
674
675        if !self.pending_patches.borrow().is_empty() {
676            log::error!(
677                "BEFORE DOM updates there are still Remaining pending patches: {}",
678                self.pending_patches.borrow().len()
679            );
680        }
681
682        self.update_dom().expect("must update dom");
683
684        // Ensure all pending patches are applied before emiting the Cmd from update
685        #[cfg(feature = "ensure-check")]
686        if !self.pending_patches.borrow().is_empty() {
687            self.apply_pending_patches()
688                .expect("applying pending patches..");
689        }
690
691        #[cfg(feature = "ensure-check")]
692        if !self.pending_patches.borrow().is_empty() {
693            log::error!(
694                "Remaining pending patches: {}",
695                self.pending_patches.borrow().len()
696            );
697            panic!(
698                "There are still pending patches.. can not emit cmd, if all pending patches
699            has not been applied yet!"
700            );
701        }
702
703        // execute this `cmd` batched pending_dispatches that may have resulted from updating the app
704        cmd.emit(self.clone());
705    }
706
707    /// Inject a style to the global document
708    fn inject_style(&mut self, class_names: String, style: &str) {
709        let style_node = html::tags::style([class(class_names)], [text(style)]);
710        let created_node = self.create_dom_node(&style_node);
711
712        let head = document().head().expect("must have a head");
713        let head_node: web_sys::Node = head.unchecked_into();
714        let dom_head = DomNode::from(head_node);
715        dom_head.append_children(vec![created_node]);
716    }
717
718    /// inject style element to the mount node
719    pub fn inject_style_to_mount(&mut self, style: &str) {
720        let style_node = html::tags::style([], [text(style)]);
721        let created_node = self.create_dom_node(&style_node);
722
723        self.mount_node
724            .borrow_mut()
725            .as_mut()
726            .expect("mount node")
727            .append_children(vec![created_node]);
728    }
729
730    /// dispatch multiple MSG
731    pub fn dispatch_multiple(&mut self, msgs: impl IntoIterator<Item = APP::MSG>) {
732        self.app_context.push_msgs(msgs);
733        self.dispatch_inner_with_priority_ric();
734    }
735
736    /// dispatch a single msg
737    pub fn dispatch(&mut self, msg: APP::MSG) {
738        self.dispatch_multiple([msg])
739    }
740
741    /// patch the DOM to reflect the App's view
742    ///
743    /// Note: This is in another function so as to allow tests to use this shared code
744    pub fn create_dom_patch(&self, new_vdom: &vdom::Node<APP::MSG>) -> Vec<DomPatch> {
745        create_dom_patch(
746            &self.root_node,
747            &self.app_context.current_vdom(),
748            new_vdom,
749            self.create_ev_callback(),
750        )
751    }
752}
753
754fn create_dom_patch<Msg, F>(
755    root_node: &Rc<RefCell<Option<DomNode>>>,
756    current_vdom: &vdom::Node<Msg>,
757    new_vdom: &vdom::Node<Msg>,
758    ev_callback: F,
759) -> Vec<DomPatch>
760where
761    Msg: 'static,
762    F: Fn(Msg) + 'static + Clone,
763{
764    let patches = diff(&current_vdom, new_vdom);
765
766    #[cfg(all(feature = "with-debug", feature = "log-patches"))]
767    {
768        log::debug!("There are {} patches", patches.len());
769        log::debug!("patches: {patches:#?}");
770    }
771
772    dom_patch::convert_patches(
773        root_node.borrow().as_ref().expect("must have a root node"),
774        &patches,
775        ev_callback,
776    )
777    .expect("must convert patches")
778}
779
780impl<APP> Program<APP>
781where
782    APP: Application,
783{
784    /// patch the DOM to reflect the App's view
785    ///
786    /// Note: This is in another function so as to allow tests to use this shared code
787    #[cfg(feature = "test-fixtures")]
788    pub fn update_dom_with_vdom(
789        &mut self,
790        new_vdom: vdom::Node<APP::MSG>,
791    ) -> Result<usize, JsValue> {
792        let dom_patches = self.create_dom_patch(&new_vdom);
793        let total_patches = dom_patches.len();
794        self.pending_patches.borrow_mut().extend(dom_patches);
795
796        self.apply_pending_patches().expect("raf");
797
798        self.app_context.set_current_dom(new_vdom);
799        Ok(total_patches)
800    }
801}