1use std::{any::Any, rc::Rc};
2
3use crate::{
4 DynamicNode,
5 diff::context::DiffContext,
6 diff::placement::{InsertionSite, insertion_site_at, splice_streamed_nodes},
7 innerlude::*,
8 mount::{RenderMode, SuspenseBranch},
9 render_driver::{RenderDriver, remove_rendered_output},
10 scope_context::SuspenseLocation,
11};
12
13#[allow(non_camel_case_types)]
15pub struct SuspenseBoundaryProps {
16 fallback: Callback<SuspenseContext, Element>,
17 children: LastRenderedNode,
19}
20
21impl Clone for SuspenseBoundaryProps {
22 fn clone(&self) -> Self {
23 Self {
24 fallback: self.fallback,
25 children: self.children.clone(),
26 }
27 }
28}
29
30#[must_use]
31#[allow(dead_code, non_camel_case_types, non_snake_case)]
32#[doc(hidden)]
33pub struct SuspenseBoundaryPropsBuilder<TypedBuilderFields> {
35 owner: Owner,
36 fields: TypedBuilderFields,
37 _phantom: (),
38}
39
40#[must_use]
41#[allow(dead_code, non_camel_case_types, non_snake_case)]
42#[doc(hidden)]
43pub struct SuspenseBoundaryComponentBuilder<RenderFn, Marker, TypedBuilderFields> {
45 render_fn: RenderFn,
46 builder: SuspenseBoundaryPropsBuilder<TypedBuilderFields>,
47 _marker: std::marker::PhantomData<fn() -> Marker>,
48}
49
50impl Properties for SuspenseBoundaryProps
51where
52 Self: Clone,
53{
54 type ComponentBuilder<RenderFn, Marker> =
55 SuspenseBoundaryComponentBuilder<RenderFn, Marker, ((), ())>;
56
57 fn component_builder<RenderFn, Marker>(
58 render_fn: RenderFn,
59 ) -> Self::ComponentBuilder<RenderFn, Marker> {
60 SuspenseBoundaryComponentBuilder {
61 render_fn,
62 builder: SuspenseBoundaryPropsBuilder {
63 owner: Owner::default(),
64 fields: ((), ()),
65 _phantom: ::core::default::Default::default(),
66 },
67 _marker: std::marker::PhantomData,
68 }
69 }
70
71 fn memoize(&mut self, new: &Self) -> bool {
72 self.fallback.__point_to(&new.fallback);
73 self.children = new.children.clone();
74 false
75 }
76}
77
78#[allow(dead_code, non_camel_case_types, missing_docs)]
79#[doc(hidden)]
80impl<RenderFn, ComponentMarker, __children>
81 SuspenseBoundaryComponentBuilder<RenderFn, ComponentMarker, ((), __children)>
82{
83 #[allow(clippy::type_complexity)]
84 #[doc(hidden)]
85 pub fn fallback<__Marker>(
86 self,
87 fallback: impl SuperInto<Callback<SuspenseContext, Element>, __Marker>,
88 ) -> SuspenseBoundaryComponentBuilder<
89 RenderFn,
90 ComponentMarker,
91 ((Callback<SuspenseContext, Element>,), __children),
92 > {
93 SuspenseBoundaryComponentBuilder {
94 render_fn: self.render_fn,
95 builder: self.builder.fallback(fallback),
96 _marker: self._marker,
97 }
98 }
99}
100
101#[allow(dead_code, non_camel_case_types, missing_docs)]
102#[doc(hidden)]
103impl<RenderFn, ComponentMarker, __fallback>
104 SuspenseBoundaryComponentBuilder<RenderFn, ComponentMarker, (__fallback, ())>
105{
106 #[allow(clippy::type_complexity)]
107 #[doc(hidden)]
108 pub fn children(
109 self,
110 children: Element,
111 ) -> SuspenseBoundaryComponentBuilder<RenderFn, ComponentMarker, (__fallback, (Element,))> {
112 SuspenseBoundaryComponentBuilder {
113 render_fn: self.render_fn,
114 builder: self.builder.children(children),
115 _marker: self._marker,
116 }
117 }
118}
119#[allow(dead_code, non_camel_case_types, non_snake_case)]
120#[doc(hidden)]
121pub trait SuspenseBoundaryPropsBuilder_Optional<T> {
123 fn into_value<F: FnOnce() -> T>(self, default: F) -> T;
125}
126impl<T> SuspenseBoundaryPropsBuilder_Optional<T> for () {
127 fn into_value<F: FnOnce() -> T>(self, default: F) -> T {
128 default()
129 }
130}
131impl<T> SuspenseBoundaryPropsBuilder_Optional<T> for (T,) {
132 fn into_value<F: FnOnce() -> T>(self, _: F) -> T {
133 self.0
134 }
135}
136#[allow(dead_code, non_camel_case_types, missing_docs)]
137#[doc(hidden)]
138impl<__children> SuspenseBoundaryPropsBuilder<((), __children)> {
139 #[allow(clippy::type_complexity)]
140 #[doc(hidden)]
141 pub fn fallback<__Marker>(
142 self,
143 fallback: impl SuperInto<Callback<SuspenseContext, Element>, __Marker>,
144 ) -> SuspenseBoundaryPropsBuilder<((Callback<SuspenseContext, Element>,), __children)> {
145 let fallback = (with_owner(self.owner.clone(), move || {
146 SuperInto::super_into(fallback)
147 }),);
148 let (_, children) = self.fields;
149 SuspenseBoundaryPropsBuilder {
150 owner: self.owner,
151 fields: (fallback, children),
152 _phantom: self._phantom,
153 }
154 }
155}
156#[allow(dead_code, non_camel_case_types, non_snake_case)]
157#[doc(hidden)]
158pub enum SuspenseBoundaryPropsBuilder_Error_Repeated_field_fallback {}
160#[allow(dead_code, non_camel_case_types, missing_docs)]
161#[doc(hidden)]
162impl<__children> SuspenseBoundaryPropsBuilder<((Callback<SuspenseContext, Element>,), __children)> {
163 #[deprecated(note = "Repeated field fallback")]
164 #[allow(clippy::type_complexity)]
165 #[doc(hidden)]
166 pub fn fallback(
167 self,
168 _: SuspenseBoundaryPropsBuilder_Error_Repeated_field_fallback,
169 ) -> SuspenseBoundaryPropsBuilder<((Callback<SuspenseContext, Element>,), __children)> {
170 self
171 }
172}
173#[allow(dead_code, non_camel_case_types, missing_docs)]
174#[doc(hidden)]
175impl<__fallback> SuspenseBoundaryPropsBuilder<(__fallback, ())> {
176 #[allow(clippy::type_complexity)]
177 #[doc(hidden)]
178 pub fn children(
179 self,
180 children: Element,
181 ) -> SuspenseBoundaryPropsBuilder<(__fallback, (Element,))> {
182 let children = (children,);
183 let (fallback, _) = self.fields;
184 SuspenseBoundaryPropsBuilder {
185 owner: self.owner,
186 fields: (fallback, children),
187 _phantom: self._phantom,
188 }
189 }
190}
191#[allow(dead_code, non_camel_case_types, non_snake_case)]
192#[doc(hidden)]
193pub enum SuspenseBoundaryPropsBuilder_Error_Repeated_field_children {}
195#[allow(dead_code, non_camel_case_types, missing_docs)]
196#[doc(hidden)]
197impl<__fallback> SuspenseBoundaryPropsBuilder<(__fallback, (Element,))> {
198 #[deprecated(note = "Repeated field children")]
199 #[allow(clippy::type_complexity)]
200 #[doc(hidden)]
201 pub fn children(
202 self,
203 _: SuspenseBoundaryPropsBuilder_Error_Repeated_field_children,
204 ) -> SuspenseBoundaryPropsBuilder<(__fallback, (Element,))> {
205 self
206 }
207}
208#[allow(dead_code, non_camel_case_types, non_snake_case)]
209#[doc(hidden)]
210pub enum SuspenseBoundaryPropsBuilder_Error_Missing_required_field_fallback {}
212#[allow(dead_code, non_camel_case_types, missing_docs, clippy::panic)]
213#[doc(hidden)]
214impl<__children> SuspenseBoundaryPropsBuilder<((), __children)> {
215 #[deprecated(note = "Missing required field fallback")]
216 #[doc(hidden)]
217 pub fn build(
218 self,
219 _: SuspenseBoundaryPropsBuilder_Error_Missing_required_field_fallback,
220 ) -> SuspenseBoundaryProps {
221 panic!()
222 }
223}
224#[allow(dead_code, non_camel_case_types, missing_docs)]
225#[doc(hidden)]
226pub struct SuspenseBoundaryPropsWithOwner {
228 inner: SuspenseBoundaryProps,
229 owner: Owner,
230}
231#[automatically_derived]
232#[allow(dead_code, non_camel_case_types, missing_docs)]
233impl ::core::clone::Clone for SuspenseBoundaryPropsWithOwner {
234 #[inline]
235 fn clone(&self) -> SuspenseBoundaryPropsWithOwner {
236 SuspenseBoundaryPropsWithOwner {
237 inner: ::core::clone::Clone::clone(&self.inner),
238 owner: ::core::clone::Clone::clone(&self.owner),
239 }
240 }
241}
242impl PartialEq for SuspenseBoundaryPropsWithOwner {
243 fn eq(&self, other: &Self) -> bool {
244 self.inner.eq(&other.inner)
245 }
246}
247impl SuspenseBoundaryPropsWithOwner {
248 pub fn into_vcomponent<M: 'static>(
250 self,
251 render_fn: impl ComponentFunction<SuspenseBoundaryProps, M>,
252 ) -> VComponent {
253 let component_name = std::any::type_name_of_val(&render_fn);
254 let render_fn_ptr = render_fn.fn_ptr();
255 let props = Box::new(VProps::new(
256 move |wrapper: Self| render_fn.rebuild(wrapper.inner),
257 <Self as Properties>::memoize,
258 self,
259 component_name,
260 ));
261 VComponent::new_with_driver(
262 component_name,
263 render_fn_ptr,
264 Rc::new(SuspenseDriver {
265 context: SuspenseContext::new(),
266 }),
267 props,
268 )
269 }
270}
271
272impl<RenderFn, Marker> ComponentBuilderRender<RenderFn, Marker> for SuspenseBoundaryPropsWithOwner
273where
274 RenderFn: ComponentFunction<SuspenseBoundaryProps, Marker>,
275 Marker: 'static,
276{
277 fn into_vcomponent(self, render_fn: RenderFn) -> VComponent {
278 SuspenseBoundaryPropsWithOwner::into_vcomponent(self, render_fn)
279 }
280}
281
282impl Properties for SuspenseBoundaryPropsWithOwner {
283 type ComponentBuilder<RenderFn, Marker> = ();
284
285 fn component_builder<RenderFn, Marker>(
286 _: RenderFn,
287 ) -> Self::ComponentBuilder<RenderFn, Marker> {
288 unreachable!()
289 }
290
291 fn memoize(&mut self, new: &Self) -> bool {
292 self.inner.memoize(&new.inner)
293 }
294}
295#[allow(dead_code, non_camel_case_types, missing_docs)]
296#[doc(hidden)]
297impl<__children: SuspenseBoundaryPropsBuilder_Optional<Element>>
298 SuspenseBoundaryPropsBuilder<((Callback<SuspenseContext, Element>,), __children)>
299{
300 #[doc(hidden)]
301 pub fn build(self) -> SuspenseBoundaryPropsWithOwner {
302 let (fallback, children) = self.fields;
303 let fallback = fallback.0;
304 let children = SuspenseBoundaryPropsBuilder_Optional::into_value(children, VNode::empty);
305 SuspenseBoundaryPropsWithOwner {
306 inner: SuspenseBoundaryProps {
307 fallback,
308 children: LastRenderedNode::new(children),
309 },
310 owner: self.owner,
311 }
312 }
313}
314
315#[allow(dead_code, non_camel_case_types, missing_docs)]
316#[doc(hidden)]
317impl<RenderFn, ComponentMarker, __children: SuspenseBoundaryPropsBuilder_Optional<Element>>
318 SuspenseBoundaryComponentBuilder<
319 RenderFn,
320 ComponentMarker,
321 ((Callback<SuspenseContext, Element>,), __children),
322 >
323{
324 #[doc(hidden)]
325 pub fn build(
326 self,
327 ) -> ComponentBuilderOutput<RenderFn, SuspenseBoundaryPropsWithOwner, ComponentMarker> {
328 ComponentBuilderOutput::new(self.render_fn, self.builder.build())
329 }
330}
331
332#[automatically_derived]
333#[allow(non_camel_case_types)]
334impl ::core::cmp::PartialEq for SuspenseBoundaryProps {
335 #[inline]
336 fn eq(&self, other: &SuspenseBoundaryProps) -> bool {
337 self.fallback == other.fallback && self.children == other.children
338 }
339}
340
341#[allow(non_snake_case)]
358#[cfg_attr(coverage_nightly, coverage(off))]
359pub fn SuspenseBoundary(_: SuspenseBoundaryProps) -> Element {
360 unreachable!("SuspenseBoundary should not be called directly")
361}
362
363struct SuspenseDriver {
365 context: SuspenseContext,
366}
367
368impl RenderDriver for SuspenseDriver {
369 fn as_any(&self) -> &dyn Any {
370 self
371 }
372
373 fn suspense_location(&self, _: SuspenseLocation) -> SuspenseLocation {
374 SuspenseLocation::SuspenseBoundary(self.context.clone())
375 }
376
377 fn create(
378 &self,
379 dom: &mut VirtualDom,
380 scope_id: ScopeId,
381 parent: Option<MountId>,
382 to: Option<&mut (dyn WriteMutations + '_)>,
383 ) -> usize {
384 if let Some(rendered) = dom.scopes[scope_id.index()]
385 .last_rendered_node
386 .as_ref()
387 .map(|rendered| rendered.node().clone())
388 {
389 dom.mark_clean(scope_id);
390 return dom.create_scope(to, scope_id, rendered, parent);
391 }
392
393 self.context.mount(scope_id);
394
395 let nodes = suspense_create(scope_id, parent, dom, to);
396 dom.mark_clean(scope_id);
397 nodes
398 }
399
400 fn diff(
401 &self,
402 dom: &mut VirtualDom,
403 scope_id: ScopeId,
404 _: Option<DiffContext<'_>>,
405 to: Option<&mut (dyn WriteMutations + '_)>,
406 ) {
407 let render_to = to.filter(|_| dom.scope_should_write_now(scope_id));
408 suspense_diff(scope_id, dom, render_to)
409 }
410
411 fn remove(
412 &self,
413 dom: &mut VirtualDom,
414 scope_id: ScopeId,
415 to: Option<&mut (dyn WriteMutations + '_)>,
416 destroy_component_state: bool,
417 ) {
418 if destroy_component_state {
425 SuspenseContext::remove_suspended_nodes(dom, scope_id, destroy_component_state);
426 }
427
428 remove_rendered_output(dom, scope_id, to, destroy_component_state);
431 }
432}
433
434fn suspense_props(dom: &VirtualDom, scope_id: ScopeId) -> &SuspenseBoundaryPropsWithOwner {
435 dom.scopes[scope_id.index()]
436 .props
437 .props()
438 .downcast_ref::<SuspenseBoundaryPropsWithOwner>()
439 .expect("suspense boundary scope carries SuspenseBoundaryPropsWithOwner")
440}
441
442fn suspense_children(dom: &VirtualDom, scope_id: ScopeId) -> LastRenderedNode {
443 suspense_props(dom, scope_id).inner.children.clone()
444}
445
446fn suspense_fallback(dom: &VirtualDom, scope_id: ScopeId) -> Callback<SuspenseContext, Element> {
447 suspense_props(dom, scope_id).inner.fallback
448}
449
450fn store_suspense_children(dom: &mut VirtualDom, scope_id: ScopeId, children: &LastRenderedNode) {
451 let props = dom.scopes[scope_id.index()]
452 .props
453 .props_mut()
454 .downcast_mut::<SuspenseBoundaryPropsWithOwner>()
455 .expect("suspense boundary scope carries SuspenseBoundaryPropsWithOwner");
456 props.inner.children.clone_from(children);
457}
458#[allow(non_snake_case)]
459#[doc(hidden)]
460mod SuspenseBoundary_completions {
461 #[doc(hidden)]
462 #[allow(non_camel_case_types)]
463 pub enum Component {
465 SuspenseBoundary {},
467 }
468}
469#[allow(unused)]
470#[doc(hidden)]
471pub use SuspenseBoundary_completions::Component::SuspenseBoundary;
472use generational_box::Owner;
473
474fn suspense_create(
478 scope_id: ScopeId,
479 parent: Option<MountId>,
480 dom: &mut VirtualDom,
481 to: Option<&mut (dyn WriteMutations + '_)>,
482) -> usize {
483 dom.runtime.clone().with_scope_on_stack(scope_id, || {
484 let suspense_context = dom.runtime.get_state(scope_id).suspense_boundary().unwrap();
485
486 let children = suspense_children(dom, scope_id);
487
488 let background = suspense_context.under_suspense_boundary(&dom.runtime(), || {
490 children.as_vnode().create_mounted(dom, parent, None)
491 });
492
493 store_suspense_children(dom, scope_id, &children);
494
495 if !suspense_context.suspended_futures().is_empty() {
498 let placeholder_context = suspense_context.clone();
499 let (node, nodes_created) =
500 suspense_context.in_suspense_placeholder(&dom.runtime(), || {
501 let fallback = suspense_fallback(dom, scope_id);
502 let branch = SuspenseBranch::new(children.as_vnode().clone(), background.mount);
503 store_suspended_branch(dom, scope_id, &branch);
504 placeholder_context.set_suspended_branch(branch);
505 let suspense_placeholder =
506 LastRenderedNode::new(fallback.call(placeholder_context));
507 let nodes_created = suspense_placeholder
508 .as_vnode()
509 .create_mounted(dom, parent, to);
510 (suspense_placeholder, nodes_created)
511 });
512
513 dom.scopes[scope_id.index()].last_rendered_node =
514 Some(MountedOutput::new(node, nodes_created.mount));
515 nodes_created.nodes
516 } else {
517 let nodes_created = suspense_context.under_suspense_boundary(&dom.runtime(), || {
520 children
521 .as_vnode()
522 .recreate_with_mount(dom, background.mount, parent, parent, to)
523 });
524 dom.scopes[scope_id.index()].last_rendered_node =
525 Some(MountedOutput::new(children, nodes_created.mount));
526 suspense_context.take_suspended_branch();
527 mark_suspense_resolved(&suspense_context, dom, scope_id);
528
529 nodes_created.nodes
530 }
531 })
532}
533
534impl SuspenseBoundaryProps {
535 #[doc(hidden)]
539 pub fn resolve_suspense<M: WriteMutations>(
540 scope_id: ScopeId,
541 dom: &mut VirtualDom,
542 to: &mut M,
543 push_replacements: impl FnOnce(&mut M) -> usize,
544 ) {
545 dom.runtime.clone().with_scope_on_stack(scope_id, || {
546 let _runtime = RuntimeGuard::new(dom.runtime());
547 let Some(scope_state) = dom.scopes.get_mut(scope_id.index()) else {
548 return;
549 };
550
551 let suspense_context = scope_state.state().suspense_boundary().unwrap().clone();
553 suspense_context.inner.suspended_tasks.borrow_mut().clear();
554
555 let currently_rendered = scope_state.last_rendered_node.clone().unwrap();
563 let mount = currently_rendered.root_mount();
564 let render_parent = dom.mounted_render_parent(mount);
565 let parent = render_parent.as_ref().map(|parent| parent.parent());
566
567 let children = suspense_children(dom, scope_id);
569 if let Some(branch) = suspense_context.take_suspended_branch() {
571 let mount = branch.root_mount();
572 branch.into_root().remove_node(mount, &mut *dom, None);
573 }
574
575 let placement = match currently_rendered.mounted_vnode().find_first_element(dom) {
580 Some(id) => InsertionSite::before(id),
581 None => insertion_site_at(currently_rendered.mounted_vnode(), dom, None),
582 };
583 splice_streamed_nodes(to, placement, push_replacements);
584 currently_rendered.as_vnode().remove_node(
585 currently_rendered.root_mount(),
586 &mut *dom,
587 Some(to),
588 );
589
590 let created = suspense_context.under_suspense_boundary(&dom.runtime(), || {
592 let mut no_op = crate::NoOpMutations;
593 children
594 .as_vnode()
595 .create_mounted(dom, parent, Some(&mut no_op))
596 });
597
598 if let Some(render_parent) = render_parent {
602 dom.set_mounted_render_parent(created.mount, render_parent);
603 }
604
605 set_rendered_children(dom, scope_id, children, created.mount);
606
607 suspense_context.run_resolved_closures(&dom.runtime);
609 })
610 }
611}
612
613fn suspense_diff(
619 scope_id: ScopeId,
620 dom: &mut VirtualDom,
621 mut to: Option<&mut (dyn WriteMutations + '_)>,
622) {
623 dom.runtime.clone().with_scope_on_stack(scope_id, || {
624 let scope = &dom.scopes[scope_id.index()];
625 let last_rendered_node = scope.last_rendered_node.clone().unwrap();
626 let children = suspense_children(dom, scope_id);
627 let fallback = suspense_fallback(dom, scope_id);
628
629 let suspense_context = scope.state().suspense_boundary().unwrap().clone();
630 let suspended_branch = suspense_context.suspended_branch();
631 let suspended = !suspense_context.suspended_futures().is_empty();
632 match (suspended_branch, suspended) {
633 (Some(suspended_branch), true) => {
636 let suspended_nodes = suspended_branch.root();
637 let suspended_mount = suspended_branch.root_mount();
638 let new_suspended_nodes: VNode = children.as_vnode().clone();
639
640 let new_suspended_mount =
644 suspense_context.under_suspense_boundary(&dom.runtime(), || {
645 MountedVNode::new(&suspended_nodes, suspended_mount).diff_node(
646 &new_suspended_nodes,
647 dom,
648 None,
649 )
650 });
651 flush_retained_branch_scopes(dom, scope_id);
652
653 if !suspense_context.suspended_futures().is_empty() {
654 let (new_placeholder, placeholder_mount) = suspense_context
656 .in_suspense_placeholder(&dom.runtime(), || {
657 let new_placeholder =
658 LastRenderedNode::new(fallback.call(suspense_context.clone()));
659 let placeholder_mount = last_rendered_node.mounted_vnode().diff_node(
660 new_placeholder.as_vnode(),
661 dom,
662 to,
663 );
664 (new_placeholder, placeholder_mount)
665 });
666 dom.scopes[scope_id.index()].last_rendered_node =
667 Some(MountedOutput::new(new_placeholder, placeholder_mount));
668 let branch = SuspenseBranch::new(new_suspended_nodes, new_suspended_mount);
669 store_suspended_branch(dom, scope_id, &branch);
670 suspense_context.set_suspended_branch(branch);
671 } else {
672 suspense_context.take_suspended_branch();
675 set_suspense_mounts_render_mode(
676 dom,
677 &new_suspended_nodes,
678 new_suspended_mount,
679 RenderMode::Foreground,
680 false,
681 );
682 suspense_context.under_suspense_boundary(&dom.runtime(), || {
683 new_suspended_nodes.remove_node_inner(
684 new_suspended_mount,
685 dom,
686 None,
687 false,
688 );
689 let mounted_children = dom
690 .current_mounted_view(new_suspended_mount)
691 .expect("suspense child");
692 replace_placeholder_with(
693 &last_rendered_node,
694 MountedVNode::new(&mounted_children, new_suspended_mount),
695 dom,
696 to.as_deref_mut(),
697 );
698 });
699 set_rendered_children(
700 dom,
701 scope_id,
702 LastRenderedNode::Real(new_suspended_nodes),
703 new_suspended_mount,
704 );
705 mark_suspense_resolved(&suspense_context, dom, scope_id);
706 }
707 }
708 (None, false) => {
710 let old_render_parent = dom.mounted_render_parent(last_rendered_node.root_mount());
711 let new_mount = suspense_context.under_suspense_boundary(&dom.runtime(), || {
712 last_rendered_node
713 .mounted_vnode()
714 .diff_node(children.as_vnode(), dom, to)
715 });
716 if let Some(old_render_parent) = old_render_parent {
717 dom.set_mounted_render_parent(new_mount, old_render_parent);
718 }
719
720 set_rendered_children(dom, scope_id, children, new_mount);
721 }
722 (None, true) => {
724 let old_children = last_rendered_node;
725 let new_children: VNode = children.as_vnode().clone();
726
727 let new_placeholder =
728 LastRenderedNode::new(fallback.call(suspense_context.clone()));
729
730 let old_children_mount = old_children.root_mount();
732 let parent = dom
733 .mounted_render_parent(old_children_mount)
734 .map(|parent| parent.parent());
735
736 let placeholder_mount =
737 suspense_context.in_suspense_placeholder(&dom.runtime(), || {
738 let created = old_children.as_vnode().move_node_to_background(
739 old_children_mount,
740 new_placeholder.as_vnode(),
741 parent,
742 dom,
743 to.as_deref_mut(),
744 );
745 created.mount
746 });
747
748 set_suspense_mounts_render_mode(
750 dom,
751 old_children.as_vnode(),
752 old_children_mount,
753 RenderMode::Background,
754 false,
755 );
756 let new_children_mount =
757 suspense_context.under_suspense_boundary(&dom.runtime(), || {
758 old_children
759 .mounted_vnode()
760 .diff_node(&new_children, dom, None)
761 });
762 flush_retained_branch_scopes(dom, scope_id);
763
764 if suspense_context.suspended_futures().is_empty() {
765 let placeholder_output = MountedOutput::new(new_placeholder, placeholder_mount);
766 set_suspense_mounts_render_mode(
767 dom,
768 &new_children,
769 new_children_mount,
770 RenderMode::Foreground,
771 false,
772 );
773 suspense_context.under_suspense_boundary(&dom.runtime(), || {
774 new_children.remove_node_inner(new_children_mount, dom, None, false);
775 let mounted_children = dom
776 .current_mounted_view(new_children_mount)
777 .expect("suspense child");
778 replace_placeholder_with(
779 &placeholder_output,
780 MountedVNode::new(&mounted_children, new_children_mount),
781 dom,
782 to.as_deref_mut(),
783 );
784 });
785 set_rendered_children(
786 dom,
787 scope_id,
788 LastRenderedNode::Real(new_children),
789 new_children_mount,
790 );
791 mark_suspense_resolved(&suspense_context, dom, scope_id);
792 } else {
793 let branch = SuspenseBranch::new(new_children, new_children_mount);
794 store_suspended_branch(dom, scope_id, &branch);
795 dom.scopes[scope_id.index()].last_rendered_node =
797 Some(MountedOutput::new(new_placeholder, placeholder_mount));
798 suspense_context.set_suspended_branch(branch);
799
800 un_resolve_suspense(dom, scope_id);
801 }
802 }
803 (Some(_), false) => {
805 let old_suspended_branch = suspense_context.take_suspended_branch().unwrap();
807 let old_suspended_mount = old_suspended_branch.root_mount();
808 let old_suspended_nodes = old_suspended_branch.root();
809
810 let mut new_children_mount = old_suspended_mount;
812 suspense_context.under_suspense_boundary(&dom.runtime(), || {
813 new_children_mount = MountedVNode::new(
814 &old_suspended_nodes,
815 old_suspended_mount,
816 )
817 .diff_node(children.as_vnode(), dom, None);
818 set_suspense_mounts_render_mode(
819 dom,
820 children.as_vnode(),
821 new_children_mount,
822 RenderMode::Foreground,
823 true,
824 );
825 let mounted_children = dom
826 .current_mounted_view(new_children_mount)
827 .expect("suspense child");
828 replace_placeholder_with(
829 &last_rendered_node,
830 MountedVNode::new(&mounted_children, new_children_mount),
831 dom,
832 to,
833 );
834 });
835
836 set_rendered_children(dom, scope_id, children, new_children_mount);
837
838 mark_suspense_resolved(&suspense_context, dom, scope_id);
839 }
840 }
841 })
842}
843
844fn flush_retained_branch_scopes(dom: &mut VirtualDom, scope_id: ScopeId) {
845 while let Some(order) = dom.pop_dirty_descendant_scope(scope_id) {
846 let dirty_scope = order.id;
847 let run_scope = dom
848 .runtime
849 .try_get_state(dirty_scope)
850 .filter(|scope| scope.should_run_during_suspense())
851 .is_some();
852 if run_scope {
853 dom.runtime.clone().while_rendering(|| {
854 dom.run_and_diff_scope(None, dirty_scope);
855 });
856 }
857 }
858}
859
860fn set_rendered_children(
861 dom: &mut VirtualDom,
862 scope_id: ScopeId,
863 children: LastRenderedNode,
864 root_mount: MountId,
865) {
866 store_suspense_children(dom, scope_id, &children);
867 dom.scopes[scope_id.index()].last_rendered_node =
868 Some(MountedOutput::new(children, root_mount));
869 dom.runtime
870 .get_state(scope_id)
871 .set_root_mount(Some(root_mount));
872}
873
874fn store_suspended_branch(dom: &mut VirtualDom, scope_id: ScopeId, branch: &SuspenseBranch) {
875 set_suspense_mounts_render_mode(
876 dom,
877 &branch.root(),
878 branch.root_mount(),
879 RenderMode::Background,
880 false,
881 );
882 store_suspense_children(dom, scope_id, &LastRenderedNode::Real(branch.root()));
883}
884
885fn replace_placeholder_with(
886 placeholder: &MountedOutput,
887 children: MountedVNode<'_>,
888 dom: &mut VirtualDom,
889 to: Option<&mut (dyn WriteMutations + '_)>,
890) {
891 let placeholder_mount = placeholder.root_mount();
894 let parent = dom
895 .mounted_render_parent(placeholder_mount)
896 .map(|parent| parent.parent());
897 let mut to = to.filter(|_| dom.mount_should_render(placeholder_mount));
898 dom.copy_render_parent_slot(placeholder_mount, children.mount());
899 if let Some(to) = to.as_deref_mut() {
903 let site = insertion_site_at(placeholder.mounted_vnode(), dom, None);
904 site.create_and_place_with_result(to, dom.runtime.clone(), |to| {
905 let created = children.vnode().recreate_with_mount(
906 dom,
907 children.mount(),
908 parent,
909 parent,
910 Some(to),
911 );
912 (created.nodes, created)
913 });
914 } else {
915 children
916 .vnode()
917 .recreate_with_mount(dom, children.mount(), parent, parent, None);
918 }
919 placeholder
920 .as_vnode()
921 .remove_node(placeholder_mount, dom, to);
922}
923
924fn mark_suspense_resolved(
926 suspense_context: &SuspenseContext,
927 dom: &mut VirtualDom,
928 scope_id: ScopeId,
929) {
930 dom.resolved_scopes.push(scope_id);
931 suspense_context.run_resolved_closures(&dom.runtime);
933}
934
935fn set_suspense_mounts_render_mode(
936 dom: &mut VirtualDom,
937 vnode: &VNode,
938 mount: MountId,
939 mode: RenderMode,
940 rerun_dirty_scopes: bool,
941) {
942 dom.set_mount_mode(mount, mode);
944
945 for anchor in vnode.dynamic_anchors() {
946 for slot in anchor.nodes() {
947 let idx = slot.index();
948 match &*slot {
949 DynamicNode::Component(_) => {
950 let scope_id = dom.unchecked_mounted_dynamic_component_scope(mount, idx);
951 if rerun_dirty_scopes && dom.mark_clean(scope_id) {
952 dom.run_and_diff_scope(None, scope_id);
953 }
954
955 let rendered = dom.scopes[scope_id.index()]
956 .last_rendered_node
957 .clone()
958 .expect("scope output");
959 set_suspense_mounts_render_mode(
960 dom,
961 rendered.as_vnode(),
962 rendered.root_mount(),
963 mode,
964 rerun_dirty_scopes,
965 );
966 }
967 DynamicNode::Fragment(nodes) => {
968 let mounts = dom.mounted_fragment_children_exact(mount, idx, nodes.len());
969 for (node, mount) in nodes.iter().zip(mounts) {
970 set_suspense_mounts_render_mode(dom, node, mount, mode, rerun_dirty_scopes);
971 }
972 }
973 DynamicNode::Text(_) => {}
974 }
975 }
976 }
977}
978
979fn un_resolve_suspense(dom: &mut VirtualDom, scope_id: ScopeId) {
981 dom.resolved_scopes.retain(|&id| id != scope_id);
982}
983
984impl SuspenseContext {
985 pub(crate) fn under_suspense_boundary<O>(&self, runtime: &Runtime, f: impl FnOnce() -> O) -> O {
987 runtime.with_suspense_location(SuspenseLocation::UnderSuspense(self.clone()), f)
988 }
989
990 pub(crate) fn in_suspense_placeholder<O>(&self, runtime: &Runtime, f: impl FnOnce() -> O) -> O {
992 runtime.with_suspense_location(SuspenseLocation::InSuspensePlaceholder(self.clone()), f)
993 }
994
995 pub fn downcast_suspense_boundary_from_scope(
997 runtime: &Runtime,
998 scope_id: ScopeId,
999 ) -> Option<Self> {
1000 runtime.try_get_state(scope_id)?.suspense_boundary()
1001 }
1002
1003 pub(crate) fn remove_suspended_nodes(
1004 dom: &mut VirtualDom,
1005 scope_id: ScopeId,
1006 destroy_component_state: bool,
1007 ) {
1008 if let Some(scope) = Self::downcast_suspense_boundary_from_scope(&dom.runtime, scope_id)
1009 && let Some(branch) = scope.take_suspended_branch()
1010 {
1011 let mount = branch.root_mount();
1012 branch
1013 .into_root()
1014 .remove_node_inner(mount, dom, None, destroy_component_state)
1015 }
1016 }
1017}