Skip to main content

dioxus_core/
runtime.rs

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
30/// A global runtime that is shared across all scopes that provides the async runtime and context API
31pub struct Runtime {
32    // We use this to track the current scope
33    // This stack should only be modified through [`Runtime::with_scope_on_stack`] to ensure that the stack is correctly restored
34    scope_stack: RefCell<Vec<ScopeId>>,
35
36    // We use this to track the current suspense location. Generally this lines up with the scope stack, but it may be different for children of a suspense boundary
37    // This stack should only be modified through [`Runtime::with_suspense_location`] to ensure that the stack is correctly restored
38    suspense_stack: RefCell<Vec<SuspenseLocation>>,
39
40    // A hand-rolled slab of scope states
41    pub(crate) scope_states: RefCell<Vec<Option<Scope>>>,
42
43    // We use this to track the current task
44    pub(crate) current_task: Cell<Option<Task>>,
45
46    /// Tasks created with cx.spawn
47    pub(crate) tasks: RefCell<slotmap::SlotMap<DefaultKey, Rc<LocalTask>>>,
48
49    // Currently suspended tasks
50    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    // The effects that need to be run after the next render
57    pub(crate) pending_effects: RefCell<BTreeSet<Effect>>,
58
59    // Tasks that are waiting to be polled
60    pub(crate) dirty_tasks: RefCell<BTreeMap<ScopeOrder, VecDeque<Task>>>,
61
62    // The renderer targets and their element id arenas.
63    pub(crate) render_targets: RefCell<Slab<RenderTargetState>>,
64
65    // Once nodes are mounted, their persistent mount identity is stored here.
66    // Each mount is associated with a whole rsx block. [`Runtime::elements`]
67    // link to a specific node in that block.
68    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    /// Get the current runtime
114    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    /// Try to get the current runtime, returning None if it doesn't exist (outside the context of a dioxus app)
154    pub fn try_current() -> Option<Rc<Self>> {
155        RUNTIMES.with(|stack| stack.borrow().last().cloned())
156    }
157
158    /// Wrap a closure so that it always runs in the runtime that is currently active
159    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    /// Run a closure with the rendering flag set to true
171    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    /// Run a closure with the rendering flag set to false
179    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    /// Get the render target currently receiving renderer mutations: the
188    /// target of the scope currently being rendered, or the root target when
189    /// no scope is active. Every scope carries a flat target assignment;
190    /// portal scopes carry the portal's target and their subtree inherits it.
191    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    /// Create a new renderer target with an isolated [`ElementId`] arena.
198    ///
199    /// Hosts serve render targets through [`MultiWriter`](crate::MultiWriter)
200    /// implementations passed into [`VirtualDom::rebuild`](crate::VirtualDom::rebuild)
201    /// and [`VirtualDom::render_immediate`](crate::VirtualDom::render_immediate). If
202    /// the host does not serve a writer for the target, those mutations are skipped.
203    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    /// Remove a render target previously created with [`create_render_target`](Self::create_render_target).
209    ///
210    /// This drops the target's [`ElementId`] arena and template
211    /// cache, freeing the slot so its [`RenderTargetId`] may be handed back out by
212    /// a later [`create_render_target`](Self::create_render_target). The root target
213    /// ([`RenderTargetId::ROOT`]) is permanent and is never removed.
214    ///
215    /// Removing a target that still has live mounts - for example because the
216    /// [`Portal`](crate::Portal) feeding it has not been torn down yet - would
217    /// leave those mounts dangling, so this leaves the target in place instead.
218    /// That happens when a host window is destroyed by the OS or during shutdown
219    /// before its owning component reclaims the target; the target is then freed
220    /// when the runtime is dropped. Callers that own the target's teardown should
221    /// remove the feeding node first so the slot can be recycled.
222    ///
223    /// Returns `true` if a target was removed, or `false` if `id` was the root,
224    /// referred to a target that was already gone, or still had live mounts.
225    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    /// Create a scope context. This slab is synchronized with the scope slab.
240    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                    // Manually drop tasks, hooks, and contexts inside of the runtime
268                    self.in_scope(id, || {
269                        // Drop all spawned tasks - order doesn't matter since tasks don't rely on eachother
270                        // In theory nested tasks might not like this
271                        for id in scope.spawned_tasks.take() {
272                            self.remove_task(id);
273                        }
274
275                        // Drop all queued effects
276                        self.pending_effects
277                            .borrow_mut()
278                            .remove(&ScopeOrder::new(scope.height, scope.id));
279
280                        // Drop all hooks in reverse order in case a hook depends on another hook.
281                        for hook in scope.hooks.take().drain(..).rev() {
282                            drop(hook);
283                        }
284
285                        // Drop all contexts
286                        scope.shared_contexts.take();
287                    });
288                } else {
289                    // Empty scopes do not need the runtime/scope stack just to clear bookkeeping.
290                    self.pending_effects
291                        .borrow_mut()
292                        .remove(&ScopeOrder::new(scope.height, scope.id));
293                }
294            }
295        }
296        // Bind the removed scope so the `borrow_mut()` temporary is released at the end of
297        // this statement, *before* the scope is dropped. Otherwise the scope's `Drop` runs
298        // while `scope_states` is still mutably borrowed and re-entrant calls into
299        // `get_state` panic with "already mutably borrowed".
300        let removed = self.scope_states.borrow_mut()[id.index()].take();
301        drop(removed);
302    }
303
304    /// Get the owner for the current scope.
305    #[track_caller]
306    pub fn current_owner<S: AnyStorage>(&self) -> Owner<S> {
307        self.get_state(self.current_scope_id()).owner()
308    }
309
310    /// Get the owner for the current scope.
311    #[track_caller]
312    pub fn scope_owner<S: AnyStorage>(&self, scope: ScopeId) -> Owner<S> {
313        self.get_state(scope).owner()
314    }
315
316    /// Get the current scope id
317    pub fn current_scope_id(&self) -> ScopeId {
318        self.scope_stack.borrow().last().copied().unwrap()
319    }
320
321    /// Try to get the current scope id, returning None if it we aren't actively inside a scope
322    pub fn try_current_scope_id(&self) -> Option<ScopeId> {
323        self.scope_stack.borrow().last().copied()
324    }
325
326    /// Call this function with the current scope set to the given scope
327    #[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    /// Get the current suspense location
334    pub(crate) fn current_suspense_location(&self) -> Option<SuspenseLocation> {
335        self.suspense_stack.borrow().last().cloned()
336    }
337
338    /// Run a callback a [`SuspenseLocation`] at the top of the stack
339    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    /// Run a callback with the current scope at the top of the stack
350    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    /// Push a scope onto the stack
357    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    /// Pop a scope off the stack
370    fn pop_scope(&self) {
371        self.suspense_stack.borrow_mut().pop();
372        self.scope_stack.borrow_mut().pop();
373    }
374
375    /// Get the state for any scope given its ID
376    ///
377    /// This is useful for inserting or removing contexts from a scope, or rendering out its root node
378    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    /// Get the state for any scope given its ID
387    ///
388    /// This is useful for inserting or removing contexts from a scope, or rendering out its root node
389    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    /// Pushes a new scope onto the stack
397    pub(crate) fn push(runtime: Rc<Runtime>) {
398        RUNTIMES.with(|stack| stack.borrow_mut().push(runtime));
399    }
400
401    /// Pops a scope off the stack
402    pub(crate) fn pop() {
403        RUNTIMES.with(|stack| stack.borrow_mut().pop().unwrap());
404    }
405
406    /// Runs a function with the current runtime
407    pub(crate) fn with<R>(callback: impl FnOnce(&Runtime) -> R) -> R {
408        callback(&Self::current())
409    }
410
411    /// Runs a function with the current scope
412    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    /// Runs a function with the current scope
417    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    /// Check if we should render a scope
423    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    /// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an element with a listener, not a static node or a text node.**
437    ///
438    /// This method will identify the appropriate element. The data must match up with the listener declared. Note that
439    /// this method does not give any indication as to the success of the listener call. If the listener is not found,
440    /// nothing will happen.
441    ///
442    /// It is up to the listeners themselves to mark nodes as dirty.
443    ///
444    /// If you have multiple events, you can call this method multiple times before calling "render_with_deadline"
445    #[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    /// Call a listener inside the VirtualDom with data from a specific render target.
451    ///
452    /// `ElementId`s are renderer-local, so multi-target renderers should use this
453    /// method instead of [`Self::handle_event`].
454    #[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                // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
532                .filter_map(move |attr| {
533                    (attr.name.get(2..) == Some(name)).then_some((path, &attr.value))
534                })
535        })
536    }
537
538    /*
539    ------------------------
540    The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
541    we find the target path.
542
543    With the target path, we try and move up to the parent until there is no parent.
544    Due to how bubbling works, we call the listeners before walking to the parent.
545
546    If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order.
547    ----------------------
548
549    For a visual demonstration, here we present a tree on the left and whether or not a listener is collected on the
550    right.
551
552    |           <-- yes (is ascendant)
553    | | |       <-- no  (is not direct ascendant)
554    | |         <-- yes (is ascendant)
555    | | | | |   <--- target element, break early, don't check other listeners
556    | | |       <-- no, broke early
557    |           <-- no, broke early
558    */
559    #[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        // If the event bubbles, we traverse through the tree until we find the target element.
572        // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
573        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            // We do this in its own block to prevent mount borrows from staying open while we call user code
581            {
582                let mounts = self.mounts.borrow();
583                let Some(mount) = mounts.get(mount_id.0) else {
584                    // If the node is suspended and not mounted, we can just ignore the event
585                    return;
586                };
587
588                let el_ref = mount.node();
589                logical_parent = mount.logical_parent();
590
591                // Accumulate listeners into the listener list bottom to top
592                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                    // Break if this is the exact target element.
602                    // This means we won't call two listeners with the same name on the same element. This should be
603                    // documented, or be rejected from the rsx! macro outright
604                    if target_path == attr_path {
605                        break;
606                    }
607                }
608            }
609
610            // Now that we've accumulated all the parent attributes for the target element, call them in
611            // reverse order. Listeners are accumulated in document order (ancestors first), so reversing
612            // calls the deepest (target) listener first and bubbles up toward the ancestors.
613            // We check the bubble state between each call to see if the event has been stopped from bubbling
614            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    /// Call an event listener in the simplest way possible without bubbling upwards
641    #[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                // If the node is suspended and not mounted, we can just ignore the event
657                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    /// Consume context from the current scope
681    pub fn consume_context<T: 'static + Clone>(&self, id: ScopeId) -> Option<T> {
682        self.get_state(id).consume_context::<T>()
683    }
684
685    /// Consume context from the current scope
686    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    /// Check if the current scope has a context
691    pub fn has_context<T: 'static + Clone>(&self, id: ScopeId) -> Option<T> {
692        self.get_state(id).has_context::<T>()
693    }
694
695    /// Provide context to the current scope
696    pub fn provide_context<T: 'static + Clone>(&self, id: ScopeId, value: T) -> T {
697        self.get_state(id).provide_context(value)
698    }
699
700    /// Get the parent of the current scope if it exists
701    pub fn parent_scope(&self, scope: ScopeId) -> Option<ScopeId> {
702        self.get_state(scope).parent_id()
703    }
704
705    /// Check if the current scope is a descendant of the given scope
706    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    /// Mark the current scope as dirty, causing it to re-render
718    pub fn needs_update(&self, scope: ScopeId) {
719        self.get_state(scope).needs_update_any(scope);
720    }
721
722    /// Get the height of the current scope
723    pub fn height(&self, id: ScopeId) -> u32 {
724        self.get_state(id).height
725    }
726
727    /// Throw a [`CapturedError`] into a scope. The error will bubble up to the nearest [`ErrorBoundary`](crate::ErrorBoundary) or the root of the app.
728    ///
729    /// # Examples
730    /// ```rust, no_run
731    /// # use dioxus::prelude::*;
732    /// fn Component() -> Element {
733    ///     let request = spawn(async move {
734    ///         match reqwest::get("https://api.example.com").await {
735    ///             Ok(_) => unimplemented!(),
736    ///             // You can explicitly throw an error into a scope with throw_error
737    ///             Err(err) => dioxus::core::Runtime::current().throw_error(ScopeId::APP, err),
738    ///         }
739    ///     });
740    ///
741    ///     unimplemented!()
742    /// }
743    /// ```
744    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    /// Get the suspense context the current scope is in
757    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    /// Force every component to be dirty and require a re-render. Used by hot-reloading.
765    ///
766    /// This might need to change to a different flag in the event hooks order changes within components.
767    /// What we really need is a way to mark components as needing a complete rebuild if they were hit by changes.
768    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    /// Check if the virtual dom is currently rendering
777    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
789/// A guard for a new runtime. This must be used to override the current runtime when importing components from a dynamic library that has it's own runtime.
790///
791/// ```rust
792/// use dioxus::prelude::*;
793/// use dioxus_core::{Runtime, RuntimeGuard};
794///
795/// fn main() {
796///     let virtual_dom = VirtualDom::new(app);
797/// }
798///
799/// fn app() -> Element {
800///     rsx! { Component { runtime: Runtime::current() } }
801/// }
802///
803/// // In a dynamic library
804/// #[derive(Props, Clone)]
805/// struct ComponentProps {
806///    runtime: std::rc::Rc<Runtime>,
807/// }
808///
809/// impl PartialEq for ComponentProps {
810///     fn eq(&self, _other: &Self) -> bool {
811///         true
812///     }
813/// }
814///
815/// fn Component(cx: ComponentProps) -> Element {
816///     use_hook(|| {
817///         let _guard = RuntimeGuard::new(cx.runtime.clone());
818///     });
819///
820///     rsx! { div {} }
821/// }
822/// ```
823pub struct RuntimeGuard(());
824
825impl RuntimeGuard {
826    /// Create a new runtime guard that sets the current Dioxus runtime. The runtime will be reset when the guard is dropped
827    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}