Synchronous prevent default (#2792)

* add prevent default methods to the event

* sync prevent default almost working

* sync prevent default working

* Move event handling into the runtime

* update core tests

* restore desktop file dialog

* implement prevent default on web

* add a hint about the new prevent default method

* fix web prevent default

* Fix CTRL+click on links

* fix values memorize in place test

* Fix a few more tests

* Add a playwright test for sync prevent default

* Fix core doc tests

* create a deprecated VirtualDom::handle_event

* fix macos imports in desktop

* Fix onmounted event

* Fix liveview support

* switch to RefCell for metadata

* Remove println

* remove prevent default attribute

* remove web specific link behavior

* Fix liveview links

* more liveview fixes for link

* Fix merge conflicts

* Fix clippy

* use the new prevent default in the file upload example
This commit is contained in:
Evan Almloff authored and GitHub committed 2024-08-13 11:57:54 -07:00
1 parent 8b62b71e2d
commit cab573eefd
54 files changed
+940 -729

No files matched your search

+4 -2
View File
@@ -56,7 +56,8 @@ pub struct ElementPath {
impl VirtualDom {
pub(crate) fn next_element(&mut self) -> ElementId {
ElementId(self.elements.insert(None))
let mut elements = self.runtime.elements.borrow_mut();
ElementId(elements.insert(None))
}
pub(crate) fn reclaim(&mut self, el: ElementId) {
@@ -71,7 +72,8 @@ impl VirtualDom {
return true;
}
self.elements.try_remove(el.0).is_some()
let mut elements = self.runtime.elements.borrow_mut();
elements.try_remove(el.0).is_some()
}
// Drop a scope without dropping its children
+5 -5
View File
@@ -161,10 +161,10 @@ impl VNode {
dom: &mut VirtualDom,
mut to: Option<&mut impl WriteMutations>,
) {
let scope = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
let scope = ScopeId(dom.get_mounted_dyn_node(mount, idx));
// Remove the scope id from the mount
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = ScopeId::PLACEHOLDER.0;
dom.set_mounted_dyn_node(mount, idx, ScopeId::PLACEHOLDER.0);
let m = self.create_component_node(mount, idx, new, parent, dom, to.as_deref_mut());
// Instead of *just* removing it, we can use the replace mutation
@@ -188,7 +188,7 @@ impl VNode {
return SuspenseBoundaryProps::create(mount, idx, component, parent, dom, to);
}
let mut scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
let mut scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx));
// If the scopeid is a placeholder, we need to load up a new scope for this vcomponent. If it's already mounted, then we can just use that
if scope_id.is_placeholder() {
@@ -198,7 +198,7 @@ impl VNode {
.id;
// Store the scope id for the next render
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = scope_id.0;
dom.set_mounted_dyn_node(mount, idx, scope_id.0);
// If this is a new scope, we also need to run it once to get the initial state
let new = dom.run_scope(scope_id);
@@ -207,7 +207,7 @@ impl VNode {
dom.scopes[scope_id.0].last_rendered_node = Some(new);
}
let scope = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
let scope = ScopeId(dom.get_mounted_dyn_node(mount, idx));
let new_node = dom.scopes[scope.0]
.last_rendered_node
+2 -1
View File
@@ -469,7 +469,8 @@ impl VNode {
) -> usize {
let template = self.template;
let mount = dom.mounts.get(self.mount.get().0).unwrap();
let mounts = dom.runtime.mounts.borrow();
let mount = mounts.get(self.mount.get().0).unwrap();
template
.roots
+42 -1
View File
@@ -10,10 +10,11 @@
#![allow(clippy::too_many_arguments)]
use crate::{
arena::MountId,
innerlude::{ElementRef, WriteMutations},
nodes::VNode,
virtual_dom::VirtualDom,
TemplateNode,
ElementId, TemplateNode,
};
mod component;
@@ -33,6 +34,46 @@ impl VirtualDom {
.sum()
}
pub(crate) fn get_mounted_parent(&self, mount: MountId) -> Option<ElementRef> {
let mounts = self.runtime.mounts.borrow();
mounts[mount.0].parent
}
pub(crate) fn get_mounted_dyn_node(&self, mount: MountId, dyn_node_idx: usize) -> usize {
let mounts = self.runtime.mounts.borrow();
mounts[mount.0].mounted_dynamic_nodes[dyn_node_idx]
}
pub(crate) fn set_mounted_dyn_node(&self, mount: MountId, dyn_node_idx: usize, value: usize) {
let mut mounts = self.runtime.mounts.borrow_mut();
mounts[mount.0].mounted_dynamic_nodes[dyn_node_idx] = value;
}
pub(crate) fn get_mounted_dyn_attr(&self, mount: MountId, dyn_attr_idx: usize) -> ElementId {
let mounts = self.runtime.mounts.borrow();
mounts[mount.0].mounted_attributes[dyn_attr_idx]
}
pub(crate) fn set_mounted_dyn_attr(
&self,
mount: MountId,
dyn_attr_idx: usize,
value: ElementId,
) {
let mut mounts = self.runtime.mounts.borrow_mut();
mounts[mount.0].mounted_attributes[dyn_attr_idx] = value;
}
pub(crate) fn get_mounted_root_node(&self, mount: MountId, root_idx: usize) -> ElementId {
let mounts = self.runtime.mounts.borrow();
mounts[mount.0].root_ids[root_idx]
}
pub(crate) fn set_mounted_root_node(&self, mount: MountId, root_idx: usize, value: ElementId) {
let mut mounts = self.runtime.mounts.borrow_mut();
mounts[mount.0].root_ids[root_idx] = value;
}
/// Remove these nodes from the dom
/// Wont generate mutations for the inner nodes
fn remove_nodes(
+47 -43
View File
@@ -19,13 +19,19 @@ impl VNode {
mut to: Option<&mut impl WriteMutations>,
) {
// The node we are diffing from should always be mounted
debug_assert!(dom.mounts.get(self.mount.get().0).is_some() || to.is_none());
debug_assert!(
dom.runtime
.mounts
.borrow()
.get(self.mount.get().0)
.is_some()
|| to.is_none()
);
// If the templates are different, we need to replace the entire template
if self.template != new.template {
let mount_id = self.mount.get();
let mount = &dom.mounts[mount_id.0];
let parent = mount.parent;
let parent = dom.get_mounted_parent(mount_id);
return self.replace(std::slice::from_ref(new), parent, dom, to);
}
@@ -62,7 +68,8 @@ impl VNode {
new.mount.set(mount_id);
if mount_id.mounted() {
let mount = &mut dom.mounts[mount_id.0];
let mut mounts = dom.runtime.mounts.borrow_mut();
let mount = &mut mounts[mount_id.0];
// Update the reference to the node for bubbling events
mount.node = new.clone_mounted();
@@ -83,8 +90,8 @@ impl VNode {
(Text(old), Text(new)) => {
// Diffing text is just a side effect, if we are diffing suspended nodes and are not outputting mutations, we can skip it
if let Some(to) = to {
let mount = &dom.mounts[mount.0];
self.diff_vtext(to, mount, idx, old, new)
let id = ElementId(dom.get_mounted_dyn_node(mount, idx));
self.diff_vtext(to, id, old, new)
}
}
(Placeholder(_), Placeholder(_)) => {}
@@ -95,7 +102,7 @@ impl VNode {
Some(self.reference_to_dynamic_node(mount, idx)),
),
(Component(old), Component(new)) => {
let scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
let scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx));
self.diff_vcomponent(
mount,
idx,
@@ -114,20 +121,20 @@ impl VNode {
// Mark the mount as unused. When a scope is created, it reads the mount and
// if it is the placeholder value, it will create the scope, otherwise it will
// reuse the scope
let old_mount = dom.mounts[mount.0].mounted_dynamic_nodes[idx];
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = usize::MAX;
let old_mount = dom.get_mounted_dyn_node(mount, idx);
dom.set_mounted_dyn_node(mount, idx, usize::MAX);
let new_nodes_on_stack =
self.create_dynamic_node(new, mount, idx, dom, to.as_deref_mut());
// Restore the mount for the scope we are removing
let new_mount = dom.mounts[mount.0].mounted_dynamic_nodes[idx];
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = old_mount;
let new_mount = dom.get_mounted_dyn_node(mount, idx);
dom.set_mounted_dyn_node(mount, idx, old_mount);
self.remove_dynamic_node(mount, dom, to, true, idx, old, Some(new_nodes_on_stack));
// Restore the mount for the node we created
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = new_mount;
dom.set_mounted_dyn_node(mount, idx, new_mount);
}
};
}
@@ -143,12 +150,14 @@ impl VNode {
}
pub(crate) fn find_first_element(&self, dom: &VirtualDom) -> ElementId {
let mount = &dom.mounts[self.mount.get().0];
let mount_id = self.mount.get();
let first = match self.get_dynamic_root_node_and_id(0) {
// This node is static, just get the root id
None => mount.root_ids[0],
None => dom.get_mounted_root_node(mount_id, 0),
// If it is dynamic and shallow, grab the id from the mounted dynamic nodes
Some((idx, Placeholder(_) | Text(_))) => ElementId(mount.mounted_dynamic_nodes[idx]),
Some((idx, Placeholder(_) | Text(_))) => {
ElementId(dom.get_mounted_dyn_node(mount_id, idx))
}
// The node is a fragment, so we need to find the first element in the fragment
Some((_, Fragment(children))) => {
let child = children.first().unwrap();
@@ -156,7 +165,7 @@ impl VNode {
}
// The node is a component, so we need to find the first element in the component
Some((id, Component(_))) => {
let scope = ScopeId(mount.mounted_dynamic_nodes[id]);
let scope = ScopeId(dom.get_mounted_dyn_node(mount_id, id));
dom.get_scope(scope)
.unwrap()
.root_node()
@@ -171,13 +180,15 @@ impl VNode {
}
pub(crate) fn find_last_element(&self, dom: &VirtualDom) -> ElementId {
let mount = &dom.mounts[self.mount.get().0];
let mount_id = self.mount.get();
let last_root_index = self.template.roots.len() - 1;
let last = match self.get_dynamic_root_node_and_id(last_root_index) {
// This node is static, just get the root id
None => mount.root_ids[last_root_index],
None => dom.get_mounted_root_node(mount_id, last_root_index),
// If it is dynamic and shallow, grab the id from the mounted dynamic nodes
Some((idx, Placeholder(_) | Text(_))) => ElementId(mount.mounted_dynamic_nodes[idx]),
Some((idx, Placeholder(_) | Text(_))) => {
ElementId(dom.get_mounted_dyn_node(mount_id, idx))
}
// The node is a fragment, so we need to find the first element in the fragment
Some((_, Fragment(children))) => {
let child = children.first().unwrap();
@@ -185,7 +196,7 @@ impl VNode {
}
// The node is a component, so we need to find the first element in the component
Some((id, Component(_))) => {
let scope = ScopeId(mount.mounted_dynamic_nodes[id]);
let scope = ScopeId(dom.get_mounted_dyn_node(mount_id, id));
dom.get_scope(scope)
.unwrap()
.root_node()
@@ -202,16 +213,8 @@ impl VNode {
/// Diff the two text nodes
///
/// This just sets the text of the node if it's different.
fn diff_vtext(
&self,
to: &mut impl WriteMutations,
mount: &VNodeMount,
idx: usize,
left: &VText,
right: &VText,
) {
fn diff_vtext(&self, to: &mut impl WriteMutations, id: ElementId, left: &VText, right: &VText) {
if left.value != right.value {
let id = ElementId(mount.mounted_dynamic_nodes[idx]);
to.set_node_text(&right.value, id);
}
}
@@ -291,7 +294,7 @@ impl VNode {
if destroy_component_state {
// Remove the mount information
dom.mounts.remove(mount.0);
dom.runtime.mounts.borrow_mut().remove(mount.0);
}
}
@@ -318,8 +321,7 @@ impl VNode {
replace_with.filter(|_| last_node),
);
} else if let Some(to) = to.as_deref_mut() {
let mount = &dom.mounts[mount.0];
let id = mount.root_ids[idx];
let id = dom.get_mounted_root_node(mount, idx);
if let (true, Some(replace_with)) = (last_node, replace_with) {
to.replace_node_with(id, replace_with);
} else {
@@ -366,11 +368,11 @@ impl VNode {
) {
match node {
Component(_comp) => {
let scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
let scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx));
dom.remove_component_node(to, destroy_component_state, scope_id, replace_with);
}
Text(_) | Placeholder(_) => {
let id = ElementId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
let id = ElementId(dom.get_mounted_dyn_node(mount, idx));
if let Some(to) = to {
if let Some(replace_with) = replace_with {
to.replace_node_with(id, replace_with);
@@ -400,7 +402,7 @@ impl VNode {
}
// only reclaim the new element if it's different from the previous one
let new_id = dom.mounts[mount.0].mounted_attributes[idx];
let new_id = dom.get_mounted_dyn_attr(mount, idx);
if Some(new_id) != next_id {
dom.reclaim(new_id);
next_id = Some(new_id);
@@ -423,7 +425,7 @@ impl VNode {
{
let mut old_attributes_iter = old_attrs.iter().peekable();
let mut new_attributes_iter = new_attrs.iter().peekable();
let attribute_id = dom.mounts[mount_id.0].mounted_attributes[idx];
let attribute_id = dom.get_mounted_dyn_attr(mount_id, idx);
let path = self.template.attr_paths[idx];
loop {
@@ -505,7 +507,8 @@ impl VNode {
path: ElementPath { path },
mount,
};
dom.elements[id.0] = Some(element_ref);
let mut elements = dom.runtime.elements.borrow_mut();
elements[id.0] = Some(element_ref);
to.create_event_listener(&attribute.name[2..], id);
}
_ => {
@@ -526,7 +529,8 @@ impl VNode {
// Initialize the mount information for this vnode if it isn't already mounted
if !self.mount.get().mounted() {
let entry = dom.mounts.vacant_entry();
let mut mounts = dom.runtime.mounts.borrow_mut();
let entry = mounts.vacant_entry();
let mount = MountId(entry.key());
self.mount.set(mount);
tracing::trace!(?self, ?mount, "creating template");
@@ -549,7 +553,7 @@ impl VNode {
// Get the mounted id of this block
// At this point, we should have already mounted the block
debug_assert!(
dom.mounts.contains(
dom.runtime.mounts.borrow().contains(
self.mount
.get()
.as_usize()
@@ -752,7 +756,7 @@ impl VNode {
for attr in &**attribute {
self.write_attribute(attribute_path, attr, id, mount, dom, to);
dom.mounts[mount.0].mounted_attributes[attribute_idx] = id;
dom.set_mounted_dyn_attr(mount, attribute_idx, id);
}
}
}
@@ -766,7 +770,7 @@ impl VNode {
) -> ElementId {
// Get an ID for this root since it's a real root
let this_id = dom.next_element();
dom.mounts[mount.0].root_ids[root_idx] = this_id;
dom.set_mounted_root_node(mount, root_idx, this_id);
to.load_template(self.template, root_idx, this_id);
@@ -789,7 +793,7 @@ impl VNode {
) -> ElementId {
// This is just the root node. We already know it's id
if let [root_idx] = path {
return dom.mounts[mount.0].root_ids[*root_idx as usize];
return dom.get_mounted_root_node(mount, *root_idx as usize);
}
// The node is deeper in the template and we should create a new id for it
@@ -835,7 +839,7 @@ impl VNode {
impl MountId {
fn mount_node(self, node_index: usize, dom: &mut VirtualDom) -> ElementId {
let id = dom.next_element();
dom.mounts[self.0].mounted_dynamic_nodes[node_index] = id.0;
dom.set_mounted_dyn_node(self, node_index, id.0);
id
}
}
+63 -14
View File
@@ -1,10 +1,6 @@
use crate::{global_context::current_scope_id, properties::SuperFrom, Runtime, ScopeId};
use generational_box::GenerationalBox;
use std::{
cell::{Cell, RefCell},
marker::PhantomData,
rc::Rc,
};
use std::{cell::RefCell, marker::PhantomData, rc::Rc};
/// A wrapper around some generic data that handles the event's state
///
@@ -26,19 +22,29 @@ use std::{
pub struct Event<T: 'static + ?Sized> {
/// The data associated with this event
pub data: Rc<T>,
pub(crate) propagates: Rc<Cell<bool>>,
pub(crate) metadata: Rc<RefCell<EventMetadata>>,
}
#[derive(Clone, Copy)]
pub(crate) struct EventMetadata {
pub(crate) propagates: bool,
pub(crate) prevent_default: bool,
}
impl<T: ?Sized + 'static> Event<T> {
pub(crate) fn new(data: Rc<T>, bubbles: bool) -> Self {
/// Create a new event from the inner data
pub fn new(data: Rc<T>, propagates: bool) -> Self {
Self {
data,
propagates: Rc::new(Cell::new(bubbles)),
metadata: Rc::new(RefCell::new(EventMetadata {
propagates,
prevent_default: false,
})),
}
}
}
impl<T> Event<T> {
impl<T: ?Sized> Event<T> {
/// Map the event data to a new type
///
/// # Example
@@ -57,7 +63,7 @@ impl<T> Event<T> {
pub fn map<U: 'static, F: FnOnce(&T) -> U>(&self, f: F) -> Event<U> {
Event {
data: Rc::new(f(&self.data)),
propagates: self.propagates.clone(),
metadata: self.metadata.clone(),
}
}
@@ -78,7 +84,12 @@ impl<T> Event<T> {
/// ```
#[deprecated = "use stop_propagation instead"]
pub fn cancel_bubble(&self) {
self.propagates.set(false);
self.metadata.borrow_mut().propagates = false;
}
/// Check if the event propagates up the tree to parent elements
pub fn propagates(&self) -> bool {
self.metadata.borrow().propagates
}
/// Prevent this event from continuing to bubble up the tree to parent elements.
@@ -96,7 +107,7 @@ impl<T> Event<T> {
/// };
/// ```
pub fn stop_propagation(&self) {
self.propagates.set(false);
self.metadata.borrow_mut().propagates = false;
}
/// Get a reference to the inner data from this event
@@ -117,12 +128,49 @@ impl<T> Event<T> {
pub fn data(&self) -> Rc<T> {
self.data.clone()
}
/// Prevent the default action of the event.
///
/// # Example
///
/// ```rust
/// # use dioxus::prelude::*;
/// fn App() -> Element {
/// rsx! {
/// a {
/// // You can prevent the default action of the event with `prevent_default`
/// onclick: move |event| {
/// event.prevent_default();
/// },
/// href: "https://dioxuslabs.com",
/// "don't go to the link"
/// }
/// }
/// }
/// ```
///
/// Note: This must be called synchronously when handling the event. Calling it after the event has been handled will have no effect.
///
/// <div class="warning">
///
/// This method is not available on the LiveView renderer because LiveView handles all events over a websocket which cannot block.
///
/// </div>
#[track_caller]
pub fn prevent_default(&self) {
self.metadata.borrow_mut().prevent_default = true;
}
/// Check if the default action of the event is enabled.
pub fn default_action_enabled(&self) -> bool {
!self.metadata.borrow().prevent_default
}
}
impl<T: ?Sized> Clone for Event<T> {
fn clone(&self) -> Self {
Self {
propagates: self.propagates.clone(),
metadata: self.metadata.clone(),
data: self.data.clone(),
}
}
@@ -138,7 +186,8 @@ impl<T> std::ops::Deref for Event<T> {
impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UiEvent")
.field("bubble_state", &self.propagates)
.field("bubble_state", &self.propagates())
.field("prevent_default", &!self.default_action_enabled())
.field("data", &self.data)
.finish()
}
+17 -11
View File
@@ -300,12 +300,14 @@ impl VNode {
let mount = self.mount.get().as_usize()?;
match &self.dynamic_nodes[dynamic_node_idx] {
DynamicNode::Text(_) | DynamicNode::Placeholder(_) => dom
.mounts
.get(mount)?
.mounted_dynamic_nodes
.get(dynamic_node_idx)
.map(|id| ElementId(*id)),
DynamicNode::Text(_) | DynamicNode::Placeholder(_) => {
let mounts = dom.runtime.mounts.borrow();
mounts
.get(mount)?
.mounted_dynamic_nodes
.get(dynamic_node_idx)
.map(|id| ElementId(*id))
}
_ => None,
}
}
@@ -314,7 +316,8 @@ impl VNode {
pub fn mounted_root(&self, root_idx: usize, dom: &VirtualDom) -> Option<ElementId> {
let mount = self.mount.get().as_usize()?;
dom.mounts.get(mount)?.root_ids.get(root_idx).copied()
let mounts = dom.runtime.mounts.borrow();
mounts.get(mount)?.root_ids.get(root_idx).copied()
}
/// Get the mounted id for a dynamic attribute index
@@ -325,7 +328,8 @@ impl VNode {
) -> Option<ElementId> {
let mount = self.mount.get().as_usize()?;
dom.mounts
let mounts = dom.runtime.mounts.borrow();
mounts
.get(mount)?
.mounted_attributes
.get(dynamic_attribute_idx)
@@ -623,7 +627,8 @@ impl VComponent {
) -> Option<ScopeId> {
let mount = vnode.mount.get().as_usize()?;
let scope_id = dom.mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
let mounts = dom.runtime.mounts.borrow();
let scope_id = mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
Some(ScopeId(scope_id))
}
@@ -641,7 +646,8 @@ impl VComponent {
) -> Option<&'a ScopeState> {
let mount = vnode.mount.get().as_usize()?;
let scope_id = dom.mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
let mounts = dom.runtime.mounts.borrow();
let scope_id = mounts.get(mount)?.mounted_dynamic_nodes[dynamic_node_index];
dom.scopes.get(scope_id)
}
@@ -791,7 +797,7 @@ impl AttributeValue {
AttributeValue::Listener(EventHandler::leak(move |event: Event<dyn Any>| {
let data = event.data.downcast::<T>().unwrap();
callback(Event {
propagates: event.propagates,
metadata: event.metadata.clone(),
data,
});
}))
+165
View File
@@ -1,4 +1,6 @@
use crate::arena::ElementRef;
use crate::innerlude::{DirtyTasks, Effect};
use crate::nodes::VNodeMount;
use crate::scope_context::SuspenseLocation;
use crate::{
innerlude::{LocalTask, SchedulerMsg},
@@ -6,13 +8,17 @@ use crate::{
scopes::ScopeId,
Task,
};
use crate::{AttributeValue, ElementId, Event};
use slab::Slab;
use slotmap::DefaultKey;
use std::any::Any;
use std::collections::BTreeSet;
use std::fmt;
use std::{
cell::{Cell, Ref, RefCell},
rc::Rc,
};
use tracing::instrument;
thread_local! {
static RUNTIMES: RefCell<Vec<Rc<Runtime>>> = const { RefCell::new(vec![]) };
@@ -48,10 +54,23 @@ pub struct Runtime {
// Tasks that are waiting to be polled
pub(crate) dirty_tasks: RefCell<BTreeSet<DirtyTasks>>,
// The element ids that are used in the renderer
// These mark a specific place in a whole rsx block
pub(crate) elements: RefCell<Slab<Option<ElementRef>>>,
// Once nodes are mounted, the information about where they are mounted is stored here
// We need to store this information on the virtual dom so that we know what nodes are mounted where when we bubble events
// Each mount is associated with a whole rsx block. [`VirtualDom::elements`] link to a specific node in the block
pub(crate) mounts: RefCell<Slab<VNodeMount>>,
}
impl Runtime {
pub(crate) fn new(sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>) -> Rc<Self> {
let mut elements = Slab::default();
// the root element is always given element ID 0 since it's the container for the entire tree
elements.insert(None);
Rc::new(Self {
sender,
rendering: Cell::new(true),
@@ -63,6 +82,8 @@ impl Runtime {
suspended_tasks: Default::default(),
pending_effects: Default::default(),
dirty_tasks: Default::default(),
elements: RefCell::new(elements),
mounts: Default::default(),
})
}
@@ -245,6 +266,150 @@ impl Runtime {
let scope = &scopes[scope_id.0].as_ref().unwrap();
!matches!(scope.suspense_location(), SuspenseLocation::UnderSuspense(suspense) if suspense.is_suspended())
}
/// 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.**
///
/// This method will identify the appropriate element. The data must match up with the listener declared. Note that
/// this method does not give any indication as to the success of the listener call. If the listener is not found,
/// nothing will happen.
///
/// It is up to the listeners themselves to mark nodes as dirty.
///
/// If you have multiple events, you can call this method multiple times before calling "render_with_deadline"
#[instrument(skip(self, event), level = "trace", name = "Runtime::handle_event")]
pub fn handle_event(self: &Rc<Self>, name: &str, event: Event<dyn Any>, element: ElementId) {
let _runtime = RuntimeGuard::new(self.clone());
let elements = self.elements.borrow();
if let Some(Some(parent_path)) = elements.get(element.0).copied() {
if event.propagates() {
self.handle_bubbling_event(parent_path, name, event);
} else {
self.handle_non_bubbling_event(parent_path, name, event);
}
}
}
/*
------------------------
The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
we find the target path.
With the target path, we try and move up to the parent until there is no parent.
Due to how bubbling works, we call the listeners before walking to the parent.
If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order.
----------------------
For a visual demonstration, here we present a tree on the left and whether or not a listener is collected on the
right.
| <-- yes (is ascendant)
| | | <-- no (is not direct ascendant)
| | <-- yes (is ascendant)
| | | | | <--- target element, break early, don't check other listeners
| | | <-- no, broke early
| <-- no, broke early
*/
#[instrument(
skip(self, uievent),
level = "trace",
name = "VirtualDom::handle_bubbling_event"
)]
fn handle_bubbling_event(&self, parent: ElementRef, name: &str, uievent: Event<dyn Any>) {
let mounts = self.mounts.borrow();
// If the event bubbles, we traverse through the tree until we find the target element.
// Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
let mut parent = Some(parent);
while let Some(path) = parent {
let mut listeners = vec![];
let Some(mount) = mounts.get(path.mount.0) else {
// If the node is suspended and not mounted, we can just ignore the event
return;
};
let el_ref = &mount.node;
let node_template = el_ref.template;
let target_path = path.path;
// Accumulate listeners into the listener list bottom to top
for (idx, this_path) in node_template.attr_paths.iter().enumerate() {
let attrs = &*el_ref.dynamic_attrs[idx];
for attr in attrs.iter() {
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
if attr.name.get(2..) == Some(name) && target_path.is_descendant(this_path) {
listeners.push(&attr.value);
// Break if this is the exact target element.
// This means we won't call two listeners with the same name on the same element. This should be
// documented, or be rejected from the rsx! macro outright
if target_path == this_path {
break;
}
}
}
}
// Now that we've accumulated all the parent attributes for the target element, call them in reverse order
// We check the bubble state between each call to see if the event has been stopped from bubbling
tracing::event!(
tracing::Level::TRACE,
"Calling {} listeners",
listeners.len()
);
for listener in listeners.into_iter().rev() {
if let AttributeValue::Listener(listener) = listener {
self.rendering.set(false);
listener.call(uievent.clone());
self.rendering.set(true);
let metadata = uievent.metadata.borrow();
if !metadata.propagates {
return;
}
}
}
let mount = el_ref.mount.get().as_usize();
parent = mount.and_then(|id| mounts.get(id).and_then(|el| el.parent));
}
}
/// Call an event listener in the simplest way possible without bubbling upwards
#[instrument(
skip(self, uievent),
level = "trace",
name = "VirtualDom::handle_non_bubbling_event"
)]
fn handle_non_bubbling_event(&self, node: ElementRef, name: &str, uievent: Event<dyn Any>) {
let mounts = self.mounts.borrow();
let Some(mount) = mounts.get(node.mount.0) else {
// If the node is suspended and not mounted, we can just ignore the event
return;
};
let el_ref = &mount.node;
let node_template = el_ref.template;
let target_path = node.path;
for (idx, this_path) in node_template.attr_paths.iter().enumerate() {
let attrs = &*el_ref.dynamic_attrs[idx];
for attr in attrs.iter() {
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
// Only call the listener if this is the exact target element.
if attr.name.get(2..) == Some(name) && target_path == this_path {
if let AttributeValue::Listener(listener) = &attr.value {
self.rendering.set(false);
listener.call(uievent.clone());
self.rendering.set(true);
break;
}
}
}
}
}
}
/// 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.
+11 -11
View File
@@ -275,7 +275,7 @@ impl SuspenseBoundaryProps {
dom: &mut VirtualDom,
to: Option<&mut M>,
) -> usize {
let mut scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
let mut scope_id = ScopeId(dom.get_mounted_dyn_node(mount, idx));
// If the ScopeId is a placeholder, we need to load up a new scope for this vcomponent. If it's already mounted, then we can just use that
if scope_id.is_placeholder() {
{
@@ -297,7 +297,7 @@ impl SuspenseBoundaryProps {
}
// Store the scope id for the next render
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = scope_id.0;
dom.set_mounted_dyn_node(mount, idx, scope_id.0);
}
dom.runtime.clone().with_scope_on_stack(scope_id, || {
let scope_state = &mut dom.scopes[scope_id.0];
@@ -404,11 +404,13 @@ impl SuspenseBoundaryProps {
// Get the parent of the suspense boundary to later create children with the right parent
let currently_rendered = scope_state.last_rendered_node.as_ref().unwrap().clone();
let mount = currently_rendered.mount.get();
let parent = dom
.mounts
.get(mount.0)
.expect("suspense placeholder is not mounted")
.parent;
let parent = {
let mounts = dom.runtime.mounts.borrow();
mounts
.get(mount.0)
.expect("suspense placeholder is not mounted")
.parent
};
let props = Self::downcast_from_props(&mut *scope_state.props).unwrap();
@@ -524,8 +526,7 @@ impl SuspenseBoundaryProps {
// Move the children to the background
let mount = old_children.mount.get();
let mount = dom.mounts.get(mount.0).expect("mount should exist");
let parent = mount.parent;
let parent = dom.get_mounted_parent(mount);
suspense_context.in_suspense_placeholder(&dom.runtime(), || {
old_children.move_node_to_background(
@@ -566,8 +567,7 @@ impl SuspenseBoundaryProps {
// Then replace the placeholder with the new children
let mount = old_placeholder.mount.get();
let mount = dom.mounts.get(mount.0).expect("mount should exist");
let parent = mount.parent;
let parent = dom.get_mounted_parent(mount);
old_placeholder.replace(
std::slice::from_ref(&*new_children),
parent,
+15 -166
View File
@@ -7,13 +7,10 @@ use crate::properties::RootProps;
use crate::root_wrapper::RootScopeWrapper;
use crate::{
arena::ElementId,
innerlude::{
ElementRef, NoOpMutations, SchedulerMsg, ScopeOrder, ScopeState, VNodeMount, VProps,
WriteMutations,
},
innerlude::{NoOpMutations, SchedulerMsg, ScopeOrder, ScopeState, VProps, WriteMutations},
runtime::{Runtime, RuntimeGuard},
scopes::ScopeId,
AttributeValue, ComponentFunction, Element, Event, Mutations,
ComponentFunction, Element, Mutations,
};
use crate::{Task, VComponent};
use futures_util::StreamExt;
@@ -97,15 +94,16 @@ use tracing::instrument;
/// let edits = vdom.rebuild_to_vec();
/// ```
///
/// To call listeners inside the VirtualDom, call [`VirtualDom::handle_event`] with the appropriate event data.
/// To call listeners inside the VirtualDom, call [`Runtime::handle_event`] with the appropriate event data.
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// # use dioxus_core::*;
/// # fn app() -> Element { rsx! { div {} } }
/// # let mut vdom = VirtualDom::new(app);
/// let event = std::rc::Rc::new(0);
/// vdom.handle_event("onclick", event, ElementId(0), true);
/// # let runtime = vdom.runtime();
/// let event = Event::new(std::rc::Rc::new(0) as std::rc::Rc<dyn std::any::Any>, true);
/// runtime.handle_event("onclick", event, ElementId(0));
/// ```
///
/// While no events are ready, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
@@ -174,7 +172,10 @@ use tracing::instrument;
/// loop {
/// tokio::select! {
/// _ = dom.wait_for_work() => {}
/// evt = real_dom.wait_for_event() => dom.handle_event("onclick", evt, ElementId(0), true),
/// evt = real_dom.wait_for_event() => {
/// let evt = dioxus_core::Event::new(evt, true);
/// dom.runtime().handle_event("onclick", evt, ElementId(0))
/// },
/// }
///
/// dom.render_immediate(&mut real_dom.apply());
@@ -206,15 +207,6 @@ pub struct VirtualDom {
pub(crate) dirty_scopes: BTreeSet<ScopeOrder>,
// The element ids that are used in the renderer
// These mark a specific place in a whole rsx block
pub(crate) elements: Slab<Option<ElementRef>>,
// Once nodes are mounted, the information about where they are mounted is stored here
// We need to store this information on the virtual dom so that we know what nodes are mounted where when we bubble events
// Each mount is associated with a whole rsx block. [`VirtualDom::elements`] link to a specific node in the block
pub(crate) mounts: Slab<VNodeMount>,
pub(crate) runtime: Rc<Runtime>,
// The scopes that have been resolved since the last render
@@ -328,8 +320,6 @@ impl VirtualDom {
runtime: Runtime::new(tx),
scopes: Default::default(),
dirty_scopes: Default::default(),
elements: Default::default(),
mounts: Default::default(),
resolved_scopes: Default::default(),
};
@@ -341,9 +331,6 @@ impl VirtualDom {
);
dom.new_scope(Box::new(root), "app");
// the root element is always given element ID 0 since it's the container for the entire tree
dom.elements.insert(None);
dom
}
@@ -423,34 +410,6 @@ impl VirtualDom {
self.queue_task(task, order);
}
/// 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.**
///
/// This method will identify the appropriate element. The data must match up with the listener declared. Note that
/// this method does not give any indication as to the success of the listener call. If the listener is not found,
/// nothing will happen.
///
/// It is up to the listeners themselves to mark nodes as dirty.
///
/// If you have multiple events, you can call this method multiple times before calling "render_with_deadline"
#[instrument(skip(self), level = "trace", name = "VirtualDom::handle_event")]
pub fn handle_event(
&mut self,
name: &str,
data: Rc<dyn Any>,
element: ElementId,
bubbles: bool,
) {
let _runtime = RuntimeGuard::new(self.runtime.clone());
if let Some(Some(parent_path)) = self.elements.get(element.0).copied() {
if bubbles {
self.handle_bubbling_event(parent_path, name, Event::new(data, bubbles));
} else {
self.handle_non_bubbling_event(parent_path, name, Event::new(data, bubbles));
}
}
}
/// Wait for the scheduler to have any work.
///
/// This method polls the internal future queue, waiting for suspense nodes, tasks, or other work. This completes when
@@ -772,121 +731,11 @@ impl VirtualDom {
self.runtime.clone()
}
/*
------------------------
The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
we find the target path.
With the target path, we try and move up to the parent until there is no parent.
Due to how bubbling works, we call the listeners before walking to the parent.
If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order.
----------------------
For a visual demonstration, here we present a tree on the left and whether or not a listener is collected on the
right.
| <-- yes (is ascendant)
| | | <-- no (is not direct ascendant)
| | <-- yes (is ascendant)
| | | | | <--- target element, break early, don't check other listeners
| | | <-- no, broke early
| <-- no, broke early
*/
#[instrument(
skip(self, uievent),
level = "trace",
name = "VirtualDom::handle_bubbling_event"
)]
fn handle_bubbling_event(&mut self, parent: ElementRef, name: &str, uievent: Event<dyn Any>) {
// If the event bubbles, we traverse through the tree until we find the target element.
// Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
let mut parent = Some(parent);
while let Some(path) = parent {
let mut listeners = vec![];
let Some(mount) = self.mounts.get(path.mount.0) else {
// If the node is suspended and not mounted, we can just ignore the event
return;
};
let el_ref = &mount.node;
let node_template = el_ref.template;
let target_path = path.path;
// Accumulate listeners into the listener list bottom to top
for (idx, this_path) in node_template.attr_paths.iter().enumerate() {
let attrs = &*el_ref.dynamic_attrs[idx];
for attr in attrs.iter() {
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
if attr.name.get(2..) == Some(name) && target_path.is_descendant(this_path) {
listeners.push(&attr.value);
// Break if this is the exact target element.
// This means we won't call two listeners with the same name on the same element. This should be
// documented, or be rejected from the rsx! macro outright
if target_path == this_path {
break;
}
}
}
}
// Now that we've accumulated all the parent attributes for the target element, call them in reverse order
// We check the bubble state between each call to see if the event has been stopped from bubbling
tracing::event!(
tracing::Level::TRACE,
"Calling {} listeners",
listeners.len()
);
for listener in listeners.into_iter().rev() {
if let AttributeValue::Listener(listener) = listener {
self.runtime.rendering.set(false);
listener.call(uievent.clone());
self.runtime.rendering.set(true);
if !uievent.propagates.get() {
return;
}
}
}
let mount = el_ref.mount.get().as_usize();
parent = mount.and_then(|id| self.mounts.get(id).and_then(|el| el.parent));
}
}
/// Call an event listener in the simplest way possible without bubbling upwards
#[instrument(
skip(self, uievent),
level = "trace",
name = "VirtualDom::handle_non_bubbling_event"
)]
fn handle_non_bubbling_event(&mut self, node: ElementRef, name: &str, uievent: Event<dyn Any>) {
let Some(mount) = self.mounts.get(node.mount.0) else {
// If the node is suspended and not mounted, we can just ignore the event
return;
};
let el_ref = &mount.node;
let node_template = el_ref.template;
let target_path = node.path;
for (idx, this_path) in node_template.attr_paths.iter().enumerate() {
let attrs = &*el_ref.dynamic_attrs[idx];
for attr in attrs.iter() {
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
// Only call the listener if this is the exact target element.
if attr.name.get(2..) == Some(name) && target_path == this_path {
if let AttributeValue::Listener(listener) = &attr.value {
self.runtime.rendering.set(false);
listener.call(uievent.clone());
self.runtime.rendering.set(true);
break;
}
}
}
}
/// Handle an event with the Virtual Dom. This method is deprecated in favor of [VirtualDom::runtime().handle_event] and will be removed in a future release.
#[deprecated = "Use [VirtualDom::runtime().handle_event] instead"]
pub fn handle_event(&self, name: &str, event: Rc<dyn Any>, element: ElementId, bubbling: bool) {
let event = crate::Event::new(event, bubbling);
self.runtime().handle_event(name, event, element);
}
}