1use crate::mount::Mount;
2use crate::scheduler::ScopeOrder;
3use crate::scope_context::SuspenseLocation;
4use crate::{AttributeValue, ElementId, Event, RenderTargetId, VNode, innerlude::MountId};
5use crate::{CapturedError, arena::RenderTargetState};
6use crate::{SuspenseContext, innerlude::Effect};
7use crate::{
8 Task,
9 innerlude::{LocalTask, SchedulerMsg},
10 scope_context::Scope,
11 scopes::ScopeId,
12};
13use generational_box::{AnyStorage, Owner, SyncStorage, UnsyncStorage};
14use slab::Slab;
15use slotmap::DefaultKey;
16use std::any::Any;
17use std::collections::{BTreeMap, BTreeSet, VecDeque};
18use std::{
19 cell::{Cell, Ref, RefCell},
20 rc::Rc,
21};
22use tracing::instrument;
23
24use dioxus_core_template::TemplatePath;
25
26thread_local! {
27 static RUNTIMES: RefCell<Vec<Rc<Runtime>>> = const { RefCell::new(vec![]) };
28}
29
30pub struct Runtime {
32 scope_stack: RefCell<Vec<ScopeId>>,
35
36 suspense_stack: RefCell<Vec<SuspenseLocation>>,
39
40 pub(crate) scope_states: RefCell<Vec<Option<Scope>>>,
42
43 pub(crate) current_task: Cell<Option<Task>>,
45
46 pub(crate) tasks: RefCell<slotmap::SlotMap<DefaultKey, Rc<LocalTask>>>,
48
49 pub(crate) suspended_tasks: Cell<usize>,
51
52 pub(crate) rendering: Cell<bool>,
53
54 pub(crate) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
55
56 pub(crate) pending_effects: RefCell<BTreeSet<Effect>>,
58
59 pub(crate) dirty_tasks: RefCell<BTreeMap<ScopeOrder, VecDeque<Task>>>,
61
62 pub(crate) render_targets: RefCell<Slab<RenderTargetState>>,
64
65 pub(crate) mounts: RefCell<Slab<Mount>>,
69}
70
71struct ScopeStackGuard<'a> {
72 runtime: &'a Runtime,
73}
74
75impl Drop for ScopeStackGuard<'_> {
76 fn drop(&mut self) {
77 self.runtime.pop_scope();
78 }
79}
80
81struct SuspenseLocationGuard<'a> {
82 runtime: &'a Runtime,
83}
84
85impl Drop for SuspenseLocationGuard<'_> {
86 fn drop(&mut self) {
87 self.runtime.suspense_stack.borrow_mut().pop();
88 }
89}
90
91impl Runtime {
92 pub(crate) fn new(sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>) -> Rc<Self> {
93 let mut render_targets = Slab::default();
94 let root = render_targets.insert(RenderTargetState::new());
95 debug_assert_eq!(root, RenderTargetId::ROOT.index());
96
97 Rc::new(Self {
98 sender,
99 rendering: Cell::new(false),
100 scope_states: Default::default(),
101 scope_stack: Default::default(),
102 suspense_stack: Default::default(),
103 current_task: Default::default(),
104 tasks: Default::default(),
105 suspended_tasks: Default::default(),
106 pending_effects: Default::default(),
107 dirty_tasks: Default::default(),
108 render_targets: RefCell::new(render_targets),
109 mounts: Default::default(),
110 })
111 }
112
113 pub fn current() -> Rc<Self> {
115 RUNTIMES
116 .with(|stack| stack.borrow().last().cloned())
117 .unwrap_or_else(|| {
118 panic!(
119 "Must be called from inside a Dioxus runtime.
120
121Help: Some APIs in dioxus require a global runtime to be present.
122If you are calling one of these APIs from outside of a dioxus runtime
123(typically in a web-sys closure or dynamic library), you will need to
124grab the runtime from a scope that has it and then move it into your
125new scope with a runtime guard.
126
127For example, if you are trying to use dioxus apis from a web-sys
128closure, you can grab the runtime from the scope it is created in:
129
130```rust
131use dioxus::prelude::*;
132static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
133
134#[component]
135fn MyComponent() -> Element {{
136 use_effect(|| {{
137 // Grab the runtime from the MyComponent scope
138 let runtime = Runtime::current().expect(\"Components run in the Dioxus runtime\");
139 // Move the runtime into the web-sys closure scope
140 let web_sys_closure = Closure::new(|| {{
141 // Then create a guard to provide the runtime to the closure
142 let _guard = RuntimeGuard::new(runtime);
143 // and run whatever code needs the runtime
144 tracing::info!(\"The count is: {{COUNT}}\");
145 }});
146 }})
147}}
148```"
149 )
150 })
151 }
152
153 pub fn try_current() -> Option<Rc<Self>> {
155 RUNTIMES.with(|stack| stack.borrow().last().cloned())
156 }
157
158 pub fn wrap_closure<'a, I, O>(f: impl Fn(I) -> O + 'a) -> impl Fn(I) -> O + 'a {
160 let current_runtime = Self::current();
161 move |input| match current_runtime.try_current_scope_id() {
162 Some(scope) => current_runtime.in_scope(scope, || f(input)),
163 None => {
164 let _runtime_guard = RuntimeGuard::new(current_runtime.clone());
165 f(input)
166 }
167 }
168 }
169
170 pub(crate) fn while_rendering<T>(&self, f: impl FnOnce() -> T) -> T {
172 self.rendering.set(true);
173 let result = f();
174 self.rendering.set(false);
175 result
176 }
177
178 pub(crate) fn while_not_rendering<T>(&self, f: impl FnOnce() -> T) -> T {
180 let previous = self.rendering.get();
181 self.rendering.set(false);
182 let result = f();
183 self.rendering.set(previous);
184 result
185 }
186
187 pub(crate) fn current_render_target_id(&self) -> RenderTargetId {
192 self.try_current_scope_id()
193 .and_then(|scope| self.try_get_state(scope).map(|state| state.target_id()))
194 .unwrap_or(RenderTargetId::ROOT)
195 }
196
197 pub fn create_render_target(&self) -> RenderTargetId {
204 let mut targets = self.render_targets.borrow_mut();
205 RenderTargetId::new(targets.insert(RenderTargetState::new()))
206 }
207
208 pub fn remove_render_target(&self, id: RenderTargetId) -> bool {
226 if id == RenderTargetId::ROOT {
227 return false;
228 }
229 let mut targets = self.render_targets.borrow_mut();
230 let Some(target) = targets.get(id.index()) else {
231 return false;
232 };
233 if target.elements.iter().any(|(_, slot)| slot.is_some()) {
234 return false;
235 }
236 targets.try_remove(id.index()).is_some()
237 }
238
239 pub(crate) fn create_scope(&self, context: Scope) {
241 let id = context.id.index();
242 let mut scopes = self.scope_states.borrow_mut();
243 if id == scopes.len() {
244 scopes.push(Some(context));
245 return;
246 }
247
248 if scopes.len() <= id {
249 scopes.resize_with(id + 1, Default::default);
250 }
251 scopes[id] = Some(context);
252 }
253
254 pub(crate) fn set_scope_target_id(&self, scope: ScopeId, target_id: RenderTargetId) {
255 self.get_state(scope).set_target_id(target_id);
256 }
257
258 pub(crate) fn remove_scope(self: &Rc<Self>, id: ScopeId) {
259 {
260 let borrow = self.scope_states.borrow();
261 if let Some(scope) = &borrow[id.index()] {
262 let has_scoped_state = !scope.spawned_tasks.borrow().is_empty()
263 || !scope.hooks.borrow().is_empty()
264 || has_user_shared_contexts(scope);
265
266 if has_scoped_state {
267 self.in_scope(id, || {
269 for id in scope.spawned_tasks.take() {
272 self.remove_task(id);
273 }
274
275 self.pending_effects
277 .borrow_mut()
278 .remove(&ScopeOrder::new(scope.height, scope.id));
279
280 for hook in scope.hooks.take().drain(..).rev() {
282 drop(hook);
283 }
284
285 scope.shared_contexts.take();
287 });
288 } else {
289 self.pending_effects
291 .borrow_mut()
292 .remove(&ScopeOrder::new(scope.height, scope.id));
293 }
294 }
295 }
296 let removed = self.scope_states.borrow_mut()[id.index()].take();
301 drop(removed);
302 }
303
304 #[track_caller]
306 pub fn current_owner<S: AnyStorage>(&self) -> Owner<S> {
307 self.get_state(self.current_scope_id()).owner()
308 }
309
310 #[track_caller]
312 pub fn scope_owner<S: AnyStorage>(&self, scope: ScopeId) -> Owner<S> {
313 self.get_state(scope).owner()
314 }
315
316 pub fn current_scope_id(&self) -> ScopeId {
318 self.scope_stack.borrow().last().copied().unwrap()
319 }
320
321 pub fn try_current_scope_id(&self) -> Option<ScopeId> {
323 self.scope_stack.borrow().last().copied()
324 }
325
326 #[track_caller]
328 pub fn in_scope<O>(self: &Rc<Self>, id: ScopeId, f: impl FnOnce() -> O) -> O {
329 let _runtime_guard = RuntimeGuard::new(self.clone());
330 self.with_scope_on_stack(id, f)
331 }
332
333 pub(crate) fn current_suspense_location(&self) -> Option<SuspenseLocation> {
335 self.suspense_stack.borrow().last().cloned()
336 }
337
338 pub(crate) fn with_suspense_location<O>(
340 &self,
341 suspense_location: SuspenseLocation,
342 f: impl FnOnce() -> O,
343 ) -> O {
344 self.suspense_stack.borrow_mut().push(suspense_location);
345 let _guard = SuspenseLocationGuard { runtime: self };
346 f()
347 }
348
349 pub(crate) fn with_scope_on_stack<O>(&self, scope: ScopeId, f: impl FnOnce() -> O) -> O {
351 self.push_scope(scope);
352 let _guard = ScopeStackGuard { runtime: self };
353 f()
354 }
355
356 fn push_scope(&self, scope: ScopeId) {
358 let suspense_location = self
359 .scope_states
360 .borrow()
361 .get(scope.index())
362 .and_then(|s| s.as_ref())
363 .map(|s| s.suspense_location())
364 .unwrap_or_default();
365 self.suspense_stack.borrow_mut().push(suspense_location);
366 self.scope_stack.borrow_mut().push(scope);
367 }
368
369 fn pop_scope(&self) {
371 self.suspense_stack.borrow_mut().pop();
372 self.scope_stack.borrow_mut().pop();
373 }
374
375 pub(crate) fn get_state(&self, id: ScopeId) -> Ref<'_, Scope> {
379 Ref::filter_map(self.scope_states.borrow(), |scopes| {
380 scopes.get(id.index()).and_then(|f| f.as_ref())
381 })
382 .ok()
383 .unwrap()
384 }
385
386 pub(crate) fn try_get_state(&self, id: ScopeId) -> Option<Ref<'_, Scope>> {
390 Ref::filter_map(self.scope_states.borrow(), |contexts| {
391 contexts.get(id.index()).and_then(|f| f.as_ref())
392 })
393 .ok()
394 }
395
396 pub(crate) fn push(runtime: Rc<Runtime>) {
398 RUNTIMES.with(|stack| stack.borrow_mut().push(runtime));
399 }
400
401 pub(crate) fn pop() {
403 RUNTIMES.with(|stack| stack.borrow_mut().pop().unwrap());
404 }
405
406 pub(crate) fn with<R>(callback: impl FnOnce(&Runtime) -> R) -> R {
408 callback(&Self::current())
409 }
410
411 pub(crate) fn with_current_scope<R>(callback: impl FnOnce(&Scope) -> R) -> R {
413 Self::with(|rt| Self::with_scope(rt.current_scope_id(), callback))
414 }
415
416 pub(crate) fn with_scope<R>(scope: ScopeId, callback: impl FnOnce(&Scope) -> R) -> R {
418 let rt = Runtime::current();
419 Self::in_scope(&rt, scope, || callback(&rt.get_state(scope)))
420 }
421
422 pub(crate) fn scope_should_render(&self, scope_id: ScopeId) -> bool {
424 let scopes = self.scope_states.borrow();
425 let scope = &scopes[scope_id.index()].as_ref().unwrap();
426 let location = scope.suspense_location();
427 if self.suspended_tasks.get() == 0 {
428 return !matches!(
429 location,
430 SuspenseLocation::UnderSuspense(boundary) if boundary.is_suspended()
431 );
432 }
433 location.should_write()
434 }
435
436 #[instrument(skip(self, event), level = "trace", name = "Runtime::handle_event")]
446 pub fn handle_event(self: &Rc<Self>, name: &str, event: Event<dyn Any>, element: ElementId) {
447 self.handle_event_for_target(RenderTargetId::ROOT, name, event, element);
448 }
449
450 #[instrument(
455 skip(self, event),
456 level = "trace",
457 name = "Runtime::handle_event_for_target"
458 )]
459 pub fn handle_event_for_target(
460 self: &Rc<Self>,
461 target_id: RenderTargetId,
462 name: &str,
463 event: Event<dyn Any>,
464 element: ElementId,
465 ) {
466 let _runtime = RuntimeGuard::new(self.clone());
467 let targets = self.render_targets.borrow();
468 let Some(target) = targets.get(target_id.index()) else {
469 return;
470 };
471
472 let parent_ref = target.elements.get(element.index()).copied().flatten();
473 drop(targets);
474
475 if let Some(parent_ref) = parent_ref
476 && let Some(path) = self.event_target_path(parent_ref, element)
477 {
478 if event.propagates() {
479 self.handle_bubbling_event(parent_ref, path, name, event);
480 } else {
481 self.handle_non_bubbling_event(parent_ref, path, name, event);
482 }
483 }
484 }
485
486 fn event_target_path(&self, mount_id: MountId, element: ElementId) -> Option<TemplatePath> {
487 let mounts = self.mounts.borrow();
488 let mount = mounts.get(mount_id.0)?;
489 let node = mount.node();
490
491 for anchor in node.dynamic_anchors() {
492 let path = anchor.static_path();
493 let Some(id) = mount.mounted_anchor_node(anchor.anchor_index()) else {
494 continue;
495 };
496 if id.element_id() == element {
497 return Some(path);
498 }
499 }
500
501 None
502 }
503
504 fn child_slot_path(&self, parent_mount: MountId, child_mount: MountId) -> Option<TemplatePath> {
505 let mounts = self.mounts.borrow();
506 let child = mounts.get(child_mount.0)?;
507 debug_assert_eq!(child.logical_parent(), Some(parent_mount));
508 let render_parent = child
509 .render_parent()
510 .expect("child mount should have a render parent");
511
512 let parent = mounts.get(parent_mount.0)?;
513 let anchor = parent.node().dynamic_anchor(render_parent.anchor_index());
514 let target = if anchor.is_parent_append_target() {
515 anchor.static_path()
516 } else {
517 anchor.static_path().split_insertion().0
518 };
519 Some(target)
520 }
521
522 fn event_attributes<'a>(
523 node: &'a VNode,
524 name: &'a str,
525 ) -> impl Iterator<Item = (TemplatePath, &'a AttributeValue)> + 'a {
526 node.dynamic_anchors().flat_map(move |anchor| {
527 let path = anchor.static_path();
528 anchor
529 .attrs()
530 .flat_map(|slot| slot.attrs())
531 .filter_map(move |attr| {
533 (attr.name.get(2..) == Some(name)).then_some((path, &attr.value))
534 })
535 })
536 }
537
538 #[instrument(
560 skip(self, path, uievent),
561 level = "trace",
562 name = "VirtualDom::handle_bubbling_event"
563 )]
564 fn handle_bubbling_event(
565 &self,
566 mount_id: MountId,
567 path: TemplatePath,
568 name: &str,
569 uievent: Event<dyn Any>,
570 ) {
571 let mut mount_id = mount_id;
574 let mut target_path = path;
575
576 loop {
577 let mut listeners = vec![];
578 let logical_parent;
579
580 {
582 let mounts = self.mounts.borrow();
583 let Some(mount) = mounts.get(mount_id.0) else {
584 return;
586 };
587
588 let el_ref = mount.node();
589 logical_parent = mount.logical_parent();
590
591 for (attr_path, value) in Self::event_attributes(el_ref, name) {
593 if !target_path.starts_with(attr_path) {
594 continue;
595 }
596
597 if let AttributeValue::Listener(listener) = value {
598 listeners.push(listener.clone());
599 }
600
601 if target_path == attr_path {
605 break;
606 }
607 }
608 }
609
610 tracing::event!(
615 tracing::Level::TRACE,
616 "Calling {} listeners",
617 listeners.len()
618 );
619 for listener in listeners.into_iter().rev() {
620 listener.call(uievent.clone());
621 let metadata = uievent.metadata.borrow();
622
623 if !metadata.propagates {
624 return;
625 }
626 }
627
628 let Some(parent_mount) = logical_parent else {
629 break;
630 };
631 let Some(slot_path) = self.child_slot_path(parent_mount, mount_id) else {
632 break;
633 };
634
635 mount_id = parent_mount;
636 target_path = slot_path;
637 }
638 }
639
640 #[instrument(
642 skip(self, target_path, uievent),
643 level = "trace",
644 name = "VirtualDom::handle_non_bubbling_event"
645 )]
646 fn handle_non_bubbling_event(
647 &self,
648 mount_id: MountId,
649 target_path: TemplatePath,
650 name: &str,
651 uievent: Event<dyn Any>,
652 ) {
653 let listeners = {
654 let mounts = self.mounts.borrow();
655 let Some(mount) = mounts.get(mount_id.0) else {
656 return;
658 };
659 let mut listeners = Vec::new();
660
661 for (attr_path, value) in Self::event_attributes(mount.node(), name) {
662 if target_path != attr_path {
663 continue;
664 }
665
666 if let AttributeValue::Listener(listener) = value {
667 listeners.push(listener.clone());
668 break;
669 }
670 }
671
672 listeners
673 };
674
675 for listener in listeners {
676 listener.call(uievent.clone());
677 }
678 }
679
680 pub fn consume_context<T: 'static + Clone>(&self, id: ScopeId) -> Option<T> {
682 self.get_state(id).consume_context::<T>()
683 }
684
685 pub fn consume_context_from_scope<T: 'static + Clone>(&self, scope_id: ScopeId) -> Option<T> {
687 self.get_state(scope_id).consume_context::<T>()
688 }
689
690 pub fn has_context<T: 'static + Clone>(&self, id: ScopeId) -> Option<T> {
692 self.get_state(id).has_context::<T>()
693 }
694
695 pub fn provide_context<T: 'static + Clone>(&self, id: ScopeId, value: T) -> T {
697 self.get_state(id).provide_context(value)
698 }
699
700 pub fn parent_scope(&self, scope: ScopeId) -> Option<ScopeId> {
702 self.get_state(scope).parent_id()
703 }
704
705 pub fn is_descendant_of(&self, us: ScopeId, other: ScopeId) -> bool {
707 let mut current = us;
708 while let Some(parent) = self.parent_scope(current) {
709 if parent == other {
710 return true;
711 }
712 current = parent;
713 }
714 false
715 }
716
717 pub fn needs_update(&self, scope: ScopeId) {
719 self.get_state(scope).needs_update_any(scope);
720 }
721
722 pub fn height(&self, id: ScopeId) -> u32 {
724 self.get_state(id).height
725 }
726
727 pub fn throw_error(&self, id: ScopeId, error: impl Into<CapturedError> + 'static) {
745 let error = error.into();
746 if let Some(cx) = self.consume_context::<crate::ErrorContext>(id) {
747 cx.insert_error(error)
748 } else {
749 tracing::error!(
750 "Tried to throw an error into an error boundary, but failed to locate a boundary: {:?}",
751 error
752 )
753 }
754 }
755
756 pub fn suspense_context(&self) -> Option<SuspenseContext> {
758 self.get_state(self.current_scope_id())
759 .suspense_location()
760 .suspense_context()
761 .cloned()
762 }
763
764 pub fn force_all_dirty(&self) {
769 self.scope_states.borrow_mut().iter().for_each(|state| {
770 if let Some(scope) = state.as_ref() {
771 scope.needs_update_any(scope.id);
772 }
773 });
774 }
775
776 pub fn vdom_is_rendering(&self) -> bool {
778 self.rendering.get()
779 }
780}
781
782fn has_user_shared_contexts(scope: &Scope) -> bool {
783 scope.shared_contexts.borrow().iter().any(|context| {
784 let context = context.as_ref();
785 !context.is::<Owner<SyncStorage>>() && !context.is::<Owner<UnsyncStorage>>()
786 })
787}
788
789pub struct RuntimeGuard(());
824
825impl RuntimeGuard {
826 pub fn new(runtime: Rc<Runtime>) -> Self {
828 Runtime::push(runtime);
829 Self(())
830 }
831}
832
833impl Drop for RuntimeGuard {
834 fn drop(&mut self) {
835 Runtime::pop();
836 }
837}
838
839#[cfg(test)]
840mod tests {
841 use super::*;
842
843 fn runtime() -> Rc<Runtime> {
844 let (sender, _receiver) = futures_channel::mpsc::unbounded();
845 Runtime::new(sender)
846 }
847
848 fn catch_expected_panic(f: impl FnOnce()) {
849 let panic_hook = std::panic::take_hook();
850 std::panic::set_hook(Box::new(|_| {}));
851 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
852 std::panic::set_hook(panic_hook);
853 assert!(result.is_err());
854 }
855
856 #[test]
857 fn with_scope_on_stack_restores_after_panic() {
858 let runtime = runtime();
859
860 catch_expected_panic(|| {
861 runtime.with_scope_on_stack(ScopeId::new(7), || panic!("forced panic"));
862 });
863
864 assert_eq!(runtime.try_current_scope_id(), None);
865 assert!(runtime.current_suspense_location().is_none());
866 }
867
868 #[test]
869 fn with_suspense_location_restores_after_panic() {
870 let runtime = runtime();
871
872 catch_expected_panic(|| {
873 runtime.with_suspense_location(SuspenseLocation::default(), || panic!("forced panic"));
874 });
875
876 assert!(runtime.current_suspense_location().is_none());
877 }
878}