1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use std::any::Any;
use crate::{
WriteMutations,
diff::context::DiffContext,
innerlude::MountId,
scope_context::SuspenseLocation,
scopes::{LastRenderedNode, ScopeId},
virtual_dom::VirtualDom,
};
/// A scope's rendering lifecycle.
///
/// Every scope owns exactly one driver, attached when its [`VComponent`] is constructed and fixed
/// for the scope's lifetime. Plain components use [`BodyDriver`], which renders the type-erased
/// props stored in [`ScopeState`]. Portal and suspense attach drivers that manage
/// `last_rendered_node` directly with no component body to run.
///
/// [`VComponent`]: crate::nodes::VComponent
/// [`ScopeState`]: crate::scopes::ScopeState
pub(crate) trait RenderDriver: 'static {
/// The driver as `Any`, for specialized driver downcasts.
fn as_any(&self) -> &dyn Any;
/// Whether `other` renders the same special lifecycle as this driver.
///
/// Plain body components are additionally identified by `VComponent::render_fn` before this
/// hook is consulted.
fn same_component(&self, other: &dyn RenderDriver) -> bool {
self.as_any().type_id() == other.as_any().type_id()
}
/// The suspense location assigned to a new scope owned by this driver.
fn suspense_location(&self, inherited: SuspenseLocation) -> SuspenseLocation {
inherited
}
/// Mount this scope's output. Maintains `scopes[id].last_rendered_node`. Returns the number of
/// nodes left on the renderer stack.
fn create(
&self,
dom: &mut VirtualDom,
scope_id: ScopeId,
parent: Option<MountId>,
to: Option<&mut (dyn WriteMutations + '_)>,
) -> usize;
/// Diff this scope's output against its current props.
fn diff(
&self,
dom: &mut VirtualDom,
scope_id: ScopeId,
parent_context: Option<DiffContext<'_>>,
to: Option<&mut (dyn WriteMutations + '_)>,
);
/// Remove this scope's output. When `destroy_component_state` is false the output is only being
/// lifted out of the real DOM and the driver must keep component state alive.
fn remove(
&self,
dom: &mut VirtualDom,
scope_id: ScopeId,
to: Option<&mut (dyn WriteMutations + '_)>,
destroy_component_state: bool,
);
}
/// Remove a scope's rendered output from the DOM, and drop the scope when
/// `destroy_component_state` is set. Shared by [`BodyDriver`] and drivers whose output is removed
/// the same way.
pub(crate) fn remove_rendered_output(
dom: &mut VirtualDom,
scope_id: ScopeId,
to: Option<&mut (dyn WriteMutations + '_)>,
destroy_component_state: bool,
) {
let node = dom.scopes[scope_id.index()]
.last_rendered_node
.clone()
.expect("scope being removed should have last_rendered_node set");
node.as_vnode()
.remove_node_inner(node.root_mount(), dom, to, destroy_component_state);
if destroy_component_state {
dom.drop_scope(scope_id);
}
}
/// The rendering lifecycle of a plain component.
pub(crate) struct BodyDriver;
impl RenderDriver for BodyDriver {
fn as_any(&self) -> &dyn Any {
self
}
fn create(
&self,
dom: &mut VirtualDom,
scope_id: ScopeId,
parent: Option<MountId>,
to: Option<&mut (dyn WriteMutations + '_)>,
) -> usize {
let existing_node = dom.scopes[scope_id.index()]
.last_rendered_node
.as_ref()
.map(|rendered| rendered.node().clone());
let new_node =
existing_node.unwrap_or_else(|| LastRenderedNode::new(dom.run_scope(scope_id)));
dom.mark_clean(scope_id);
dom.create_scope(to, scope_id, new_node, parent)
}
fn diff(
&self,
dom: &mut VirtualDom,
scope_id: ScopeId,
parent_context: Option<DiffContext<'_>>,
to: Option<&mut (dyn WriteMutations + '_)>,
) {
let body = dom.run_scope(scope_id);
dom.diff_scope(to, scope_id, body, parent_context);
}
fn remove(
&self,
dom: &mut VirtualDom,
scope_id: ScopeId,
to: Option<&mut (dyn WriteMutations + '_)>,
destroy_component_state: bool,
) {
remove_rendered_output(dom, scope_id, to, destroy_component_state);
}
}