@@ -1,16 +1,63 @@
11use super::change_list::ChangeList;
2−use super::node::{Attribute, ElementNode, Node, TextNode};
3−use super::Render;
2+use super::node::{Attribute, ElementNode, Listener, Node, TextNode};
3+use super::RootRender;
4+use crate::events::EventsRegistry;
45use bumpalo::Bump;
6+use futures::future::Future;
57use log::*;
8+use std::cell::Cell;
9+use std::cell::RefCell;
610use std::cmp;
11+use std::fmt;
712use std::mem;
13+use std::mem::ManuallyDrop;
14+use std::rc::{Rc, Weak};
15+use wasm_bindgen::prelude::*;
16+use wasm_bindgen::JsCast;
17+use wasm_bindgen_futures::JsFuture;
18+
19+/// A strong handle to a mounted virtual DOM.
20+///
21+/// When this handle is dropped, the virtual DOM is unmounted and its listeners
22+/// removed. To keep it mounted forever, use the `Vdom::forget` method.
23+#[must_use = "A `Vdom` will only keep rendering and listening to events while it has not been \
24+ dropped. If you want a `Vdom` to run forever, call `Vdom::forget`."]
25+pub struct Vdom {
26+ inner: Rc<VdomInner>,
27+}
28+
29+/// A weak handle to a virtual DOM.
30+///
31+/// Does not prevent the virtual DOM from being unmounted: only keeping the
32+/// original `Vdom` alive guarantees that.
33+///
34+/// A `VdomWeak` also gives you the capability to scheduling re-rendering (say
35+/// after mutating the render component state).
36+#[derive(Clone, Debug)]
37+pub struct VdomWeak {
38+ inner: Weak<VdomInner>,
39+}
40+
41+#[derive(Debug)]
42+pub(crate) struct VdomInner {
43+ pub(crate) shared: VdomInnerShared,
44+ pub(crate) exclusive: RefCell<VdomInnerExclusive>,
45+}
46+
47+pub(crate) struct VdomInnerShared {
48+ pub(crate) render_scheduled: Cell<Option<js_sys::Promise>>,
49+}
50+
51+pub(crate) struct VdomInnerExclusive {
52+ // Always `Some` except just before we drop. Just an option so that
53+ // `unmount` can take the component out but we can still have a Drop
54+ // implementation.
55+ component: Option<Box<RootRender>>,
856
9−pub struct Vdom<R> {
10− component: R,
1157 dom_buffers: [Bump; 2],
12− change_list: ChangeList,
58+ change_list: ManuallyDrop<ChangeList>,
1359 container: web_sys::Element,
60+ events_registry: Option<Rc<RefCell<EventsRegistry>>>,
1461
1562 // Actually a reference into `self.dom_buffers[0]` or if `self.component` is
1663 // caching renders, into `self.component`'s bump.
@@ -21,19 +68,77 @@ unsafe fn extend_node_lifetime<'a>(node: Node<'a>) -> Node<'static> {
2168 mem::transmute(node)
2269}
2370
24−impl<R> Vdom<R>
25−where
26− R: Render,
27−{
28− pub fn new(container: &web_sys::Element, component: R) -> Vdom<R> {
71+impl fmt::Debug for VdomInnerShared {
72+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
73+ let render_scheduled = Cell::new(None);
74+ self.render_scheduled.swap(&render_scheduled);
75+ let render_scheduled = render_scheduled.into_inner();
76+ let r = f
77+ .debug_struct("VdomInnerShared")
78+ .field("render_scheduled", &render_scheduled)
79+ .finish();
80+ self.render_scheduled.set(render_scheduled);
81+ r
82+ }
83+}
84+
85+impl fmt::Debug for VdomInnerExclusive {
86+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
87+ f.debug_struct("VdomInnerExclusive")
88+ .field("component", &"..")
89+ .field("dom_buffers", &self.dom_buffers)
90+ .field("change_list", &self.change_list)
91+ .field("container", &self.container)
92+ .field("events_registry", &self.events_registry)
93+ .field("current_root", &self.current_root)
94+ .finish()
95+ }
96+}
97+
98+impl Drop for VdomInnerExclusive {
99+ fn drop(&mut self) {
100+ debug!("Dropping VdomInnerExclusive");
101+
102+ // Make sure that we clean up our JS listeners and all that before we
103+ // empty the container.
104+ unsafe {
105+ ManuallyDrop::drop(&mut self.change_list);
106+ }
107+
108+ let registry = self.events_registry.take().unwrap_throw();
109+ let mut registry = registry.borrow_mut();
110+ registry.clear_active_listeners();
111+
112+ self.container.set_inner_html("");
113+ }
114+}
115+
116+impl Vdom {
117+ /// Mount a new `Vdom` in the given container element with the given root
118+ /// rendering component.
119+ ///
120+ /// This will box the given component into trait object.
121+ pub fn new<R>(container: &web_sys::Element, component: R) -> Vdom
122+ where
123+ R: RootRender,
124+ {
125+ Self::with_boxed_root_render(container, Box::new(component) as Box<RootRender>)
126+ }
127+
128+ /// Construct a `Vdom` with the already-boxed-as-a-trait-object root
129+ /// rendering component.
130+ pub fn with_boxed_root_render(
131+ container: &web_sys::Element,
132+ component: Box<RootRender>,
133+ ) -> Vdom {
29134 let dom_buffers = [Bump::new(), Bump::new()];
30− let change_list = ChangeList::new(container);
135+ let change_list = ManuallyDrop::new(ChangeList::new(container));
31136
32137 // Ensure that the container is empty.
33138 container.set_inner_html("");
34139
35140 // Create a dummy `<div/>` in our container.
36− let current_root = Node::element(&dom_buffers[0], "div", [], []);
141+ let current_root = Node::element(&dom_buffers[0], "div", [], [], []);
37142 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
38143 let window = web_sys::window().expect("should have acess to the Window");
39144 let document = window
@@ -48,76 +153,114 @@ where
48153 )
49154 .expect("should append child OK");
50155
51− // Diff and apply the `contents` against our dummy `<div/>`.
52156 let container = container.clone();
53− let mut vdom = Vdom {
54− component,
55− dom_buffers,
56− change_list,
57− container,
58− current_root,
59− };
60− vdom.render();
61− vdom
157+ let inner = Rc::new(VdomInner {
158+ shared: VdomInnerShared {
159+ render_scheduled: Cell::new(None),
160+ },
161+ exclusive: RefCell::new(VdomInnerExclusive {
162+ component: Some(component),
163+ dom_buffers,
164+ change_list,
165+ container,
166+ current_root,
167+ events_registry: None,
168+ }),
169+ });
170+
171+ let (events_registry, events_trampoline) = EventsRegistry::new(Rc::downgrade(&inner));
172+
173+ {
174+ let mut inner = inner.exclusive.borrow_mut();
175+ inner.events_registry = Some(events_registry);
176+ inner.change_list.init_events_trampoline(events_trampoline);
177+
178+ // Diff and apply the `contents` against our dummy `<div/>`.
179+ inner.render();
180+ }
181+
182+ Vdom { inner }
62183 }
63184
64− /// Get a shared reference to the underlying render component.
185+ /// Run this virtual DOM and its listeners forever and never unmount it.
65186 #[inline]
66− pub fn component(&self) -> &R {
67− &self.component
187+ pub fn forget(self) {
188+ mem::forget(self);
68189 }
69190
70− /// Get an exclusive reference to the underlying render component.
191+ /// Get a weak handle to this virtual DOM.
71192 #[inline]
72− pub fn component_mut(&mut self) -> &mut R {
73− &mut self.component
193+ pub fn weak(&self) -> VdomWeak {
194+ VdomWeak::new(&self.inner)
74195 }
75196
76− /// Take the render component out of this virtual DOM.
197+ /// Unmount this virtual DOM, unregister its event listeners, and return its
198+ /// root render component.
77199 #[inline]
78− pub fn into_component(self) -> R {
79− self.component
200+ pub fn unmount(self) -> Box<RootRender> {
201+ Rc::try_unwrap(self.inner.clone())
202+ .map_err(|_| ())
203+ .unwrap_throw()
204+ .exclusive
205+ .into_inner()
206+ .component
207+ .take()
208+ .unwrap_throw()
80209 }
210+}
81211
82− /// Get a reference to the physical DOM node that reflects this virtual DOM
83− /// tree.
84− pub fn container(&self) -> &web_sys::Element {
85− &self.container
212+impl VdomInnerExclusive {
213+ /// Get an exclusive reference to the underlying render component as a raw
214+ /// trait object.
215+ #[inline]
216+ pub(crate) fn component_raw_mut(&mut self) -> &mut dyn RootRender {
217+ &mut **self.component.as_mut().unwrap_throw()
86218 }
87219
88220 /// Re-render this virtual dom's current component.
89− pub fn render(&mut self) {
221+ pub(crate) fn render(&mut self) {
90222 unsafe {
91− self.dom_buffers[1].reset();
92− let new_contents = self.component.render(&self.dom_buffers[1]);
93− let new_contents = extend_node_lifetime(new_contents);
223+ let events_registry = self.events_registry.take().unwrap();
224+
225+ {
226+ // All the old listeners are no longer active. We will build a new
227+ // set of active listeners when diffing.
228+ //
229+ // NB: if we end up avoiding diffing cached renders (instead of just
230+ // avoiding re-rendering them) then we will need to maintain cached
231+ // active listeners, and can't just clear all active listeners and
232+ // rebuild them here.
233+ let mut registry = events_registry.borrow_mut();
234+ registry.clear_active_listeners();
235+
236+ // Reset the inactive bump arena's pointer.
237+ self.dom_buffers[1].reset();
94238
95− let old_contents = self.current_root.take().unwrap();
96− self.diff(old_contents, new_contents.clone());
239+ // Render the new current contents into the inactive bump arena.
240+ let new_contents = self
241+ .component
242+ .as_ref()
243+ .unwrap_throw()
244+ .render(&self.dom_buffers[1]);
245+ let new_contents = extend_node_lifetime(new_contents);
246+
247+ // Diff the old contents with the new contents.
248+ let old_contents = self.current_root.take().unwrap();
249+ self.diff(&mut registry, old_contents, new_contents.clone());
250+
251+ // Swap the buffers to make the bump arena with the new contents the
252+ // active arena, and the old one into the inactive arena.
253+ self.swap_buffers();
254+ self.set_current_root(new_contents);
255+ }
97256
98− self.swap_buffers();
99− self.set_current_root(new_contents);
257+ self.events_registry = Some(events_registry);
100258
259+ // Tell JS to apply our diff-generated changes to the physical DOM!
101260 self.change_list.apply_changes();
102261 }
103262 }
104263
105− /// Render a new component.
106− pub fn render_component<S>(self, component: S) -> Vdom<S>
107− where
108− S: Render,
109− {
110− let mut vdom = Vdom {
111− component,
112− dom_buffers: self.dom_buffers,
113− change_list: self.change_list,
114− container: self.container,
115− current_root: self.current_root,
116− };
117− vdom.render();
118− vdom
119− }
120−
121264 fn swap_buffers(&mut self) {
122265 let (first, second) = self.dom_buffers.as_mut().split_at_mut(1);
123266 mem::swap(&mut first[0], &mut second[0]);
@@ -128,7 +271,7 @@ where
128271 self.current_root = Some(current);
129272 }
130273
131− fn diff(&self, old: Node, new: Node) {
274+ fn diff<'a>(&self, registry: &mut EventsRegistry, old: Node<'a>, new: Node<'a>) {
132275 // debug!("---------------------------------------------------------");
133276 // debug!("dodrio::Vdom::diff");
134277 // debug!(" old = {:#?}", old);
@@ -143,22 +286,24 @@ where
143286 }
144287 (&Node::Text(TextNode { .. }), Node::Element(ElementNode { .. })) => {
145288 debug!(" replacing a text node with an element");
146− self.create(new);
289+ self.create(registry, new);
147290 self.change_list.emit_replace_with();
148291 }
149292 (&Node::Element(ElementNode { .. }), Node::Text(TextNode { .. })) => {
150293 debug!(" replacing an element with a text node");
151− self.create(new);
294+ self.create(registry, new);
152295 self.change_list.emit_replace_with();
153296 }
154297 (
155298 &Node::Element(ElementNode {
156299 tag_name: new_tag_name,
300+ listeners: new_listeners,
157301 attributes: new_attributes,
158302 children: new_children,
159303 }),
160304 Node::Element(ElementNode {
161305 tag_name: old_tag_name,
306+ listeners: old_listeners,
162307 attributes: old_attributes,
163308 children: old_children,
164309 }),
@@ -166,16 +311,48 @@ where
166311 debug!(" updating an element");
167312 if new_tag_name != old_tag_name {
168313 debug!(" different tag names; creating new element and replacing old element");
169− self.create(new);
314+ self.create(registry, new);
170315 self.change_list.emit_replace_with();
171316 return;
172317 }
318+ self.diff_listeners(registry, old_listeners, new_listeners);
173319 self.diff_attributes(old_attributes, new_attributes);
174− self.diff_children(old_children, new_children);
320+ self.diff_children(registry, old_children, new_children);
175321 }
176322 }
177323 }
178324
325+ fn diff_listeners<'a>(
326+ &self,
327+ registry: &mut EventsRegistry,
328+ old: &'a [Listener<'a>],
329+ new: &'a [Listener<'a>],
330+ ) {
331+ debug!(" updating event listeners");
332+
333+ 'outer1: for new_l in new {
334+ unsafe {
335+ registry.add(new_l);
336+ }
337+ for old_l in old {
338+ if new_l.event == old_l.event {
339+ self.change_list.emit_update_event_listener(new_l);
340+ continue 'outer1;
341+ }
342+ }
343+ self.change_list.emit_new_event_listener(new_l);
344+ }
345+
346+ 'outer2: for old_l in old {
347+ for new_l in new {
348+ if new_l.event == old_l.event {
349+ continue 'outer2;
350+ }
351+ }
352+ self.change_list.emit_remove_event_listener(old_l.event);
353+ }
354+ }
355+
179356 fn diff_attributes(&self, old: &[Attribute], new: &[Attribute]) {
180357 debug!(" updating attributes");
181358
@@ -205,7 +382,12 @@ where
205382 }
206383 }
207384
208− fn diff_children(&self, old: &[Node], new: &[Node]) {
385+ fn diff_children<'a>(
386+ &self,
387+ registry: &mut EventsRegistry,
388+ old: &'a [Node<'a>],
389+ new: &'a [Node<'a>],
390+ ) {
209391 debug!(" updating children shared by old and new");
210392
211393 let num_children_to_diff = cmp::min(new.len(), old.len());
@@ -227,7 +409,7 @@ where
227409 self.change_list.emit_pop_push_next_sibling();
228410 }
229411
230− self.diff(old_child.clone(), new_child.clone());
412+ self.diff(registry, old_child.clone(), new_child.clone());
231413 }
232414
233415 if old_children.next().is_some() {
@@ -247,7 +429,7 @@ where
247429 self.change_list.emit_pop();
248430 pushed = false;
249431 }
250− self.create(new_child.clone());
432+ self.create(registry, new_child.clone());
251433 self.change_list.emit_append_child();
252434 }
253435 }
@@ -258,25 +440,191 @@ where
258440 }
259441 }
260442
261− fn create(&self, node: Node) {
443+ fn create<'a>(&self, registry: &mut EventsRegistry, node: Node<'a>) {
262444 match node {
263445 Node::Text(TextNode { text }) => {
264446 self.change_list.emit_create_text_node(text);
265447 }
266448 Node::Element(ElementNode {
267449 tag_name,
450+ listeners,
268451 attributes,
269452 children,
270453 }) => {
271454 self.change_list.emit_create_element(tag_name);
455+ for l in listeners {
456+ unsafe {
457+ registry.add(l);
458+ }
459+ self.change_list.emit_new_event_listener(l);
460+ }
272461 for attr in attributes {
273462 self.change_list.emit_set_attribute(&attr.name, &attr.value);
274463 }
275464 for child in children {
276− self.create(child.clone());
465+ self.create(registry, child.clone());
277466 self.change_list.emit_append_child();
278467 }
279468 }
280469 }
281470 }
282471}
472+
473+fn request_animation_frame(f: &Closure<FnMut()>) {
474+ web_sys::window()
475+ .expect_throw("should have a window")
476+ .request_animation_frame(f.as_ref().unchecked_ref())
477+ .expect_throw("should register `requestAnimationFrame` OK");
478+}
479+
480+fn with_animation_frame<F>(mut f: F)
481+where
482+ F: 'static + FnMut(),
483+{
484+ let g = Rc::new(RefCell::new(None));
485+ let h = g.clone();
486+
487+ let f = Closure::wrap(Box::new(move || {
488+ *g.borrow_mut() = None;
489+ f();
490+ }) as Box<FnMut()>);
491+ request_animation_frame(&f);
492+
493+ *h.borrow_mut() = Some(f);
494+}
495+
496+trait CellOptionExt<T> {
497+ fn take(&self) -> Option<T>;
498+}
499+
500+impl<T> CellOptionExt<T> for Cell<Option<T>> {
501+ fn take(&self) -> Option<T> {
502+ let temp = Cell::new(None);
503+ self.swap(&temp);
504+ temp.into_inner()
505+ }
506+}
507+
508+/// An operation failed because the virtual DOM was already dropped and
509+/// unmounted.
510+#[derive(Debug)]
511+pub struct VdomDroppedError {}
512+
513+impl fmt::Display for VdomDroppedError {
514+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
515+ write!(f, "The virtual DOM was dropped.")
516+ }
517+}
518+
519+impl std::error::Error for VdomDroppedError {}
520+
521+impl VdomWeak {
522+ /// Construct a new weak handle to the given virtual DOM.
523+ #[inline]
524+ pub(crate) fn new(inner: &Rc<VdomInner>) -> VdomWeak {
525+ VdomWeak {
526+ inner: Rc::downgrade(inner),
527+ }
528+ }
529+
530+ /// TODO
531+ pub fn set_component(
532+ self,
533+ root: Box<dyn RootRender>,
534+ ) -> impl Future<Item = Box<dyn RootRender + 'static>, Error = VdomDroppedError> {
535+ futures::future::ok(self.inner.upgrade())
536+ .and_then(|inner| inner.ok_or(()))
537+ .map_err(|_| VdomDroppedError {})
538+ .and_then(|inner| {
539+ let promise = js_sys::Promise::resolve(&JsValue::null());
540+ JsFuture::from(promise)
541+ .map_err(|_| VdomDroppedError {})
542+ .and_then(move |_| {
543+ let old = {
544+ let mut exclusive = inner.exclusive.borrow_mut();
545+ mem::replace(&mut *exclusive.component.as_mut().unwrap_throw(), root)
546+ };
547+ VdomWeak::new(&inner).render().map(|_| old)
548+ })
549+ })
550+ }
551+
552+ /// Execute `f` with a reference to this virtual DOM's root rendering
553+ /// component.
554+ ///
555+ /// To ensure exclusive access to the root rendering component, the
556+ /// invocation takes place on a new tick of the micro-task queue.
557+ pub fn with_component<F, T>(&self, f: F) -> impl Future<Item = T, Error = VdomDroppedError>
558+ where
559+ F: 'static + FnOnce(&mut dyn RootRender) -> T,
560+ {
561+ futures::future::ok(self.inner.upgrade())
562+ .and_then(|inner| inner.ok_or(()))
563+ .map_err(|_| VdomDroppedError {})
564+ .and_then(|inner| {
565+ let mut f = Some(f);
566+ let promise = js_sys::Promise::resolve(&JsValue::null());
567+ JsFuture::from(promise)
568+ .map_err(|_| VdomDroppedError {})
569+ .map(move |_| {
570+ let f = f.take().unwrap_throw();
571+ let mut exclusive = inner.exclusive.borrow_mut();
572+ f(exclusive.component_raw_mut())
573+ })
574+ })
575+ }
576+
577+ /// Schedule a render to occur during the next animation frame.
578+ ///
579+ /// If you want a future that resolves after the render has finished, use
580+ /// `render` instead.
581+ pub fn schedule_render(&self) {
582+ debug!("VdomWeak::schedule_render");
583+ wasm_bindgen_futures::spawn_local(self.render().map_err(|_| ()));
584+ }
585+
586+ /// Schedule a render to occur during the next animation frame and return a
587+ /// future that will complete once the render has finished.
588+ ///
589+ /// If you don't want to do more things after the render completes, then use
590+ /// `schedule_render` instead of `render`.
591+ pub fn render(&self) -> impl Future<Item = (), Error = VdomDroppedError> {
592+ debug!("VdomWeak::render: initiating render in new animation frame");
593+ futures::future::ok(self.inner.upgrade())
594+ .and_then(|inner| inner.ok_or(()))
595+ .map_err(|_| VdomDroppedError {})
596+ .and_then(|inner| {
597+ let promise = inner.shared.render_scheduled.take().unwrap_or_else(|| {
598+ js_sys::Promise::new(&mut |resolve, reject| {
599+ let vdom = VdomWeak {
600+ inner: Rc::downgrade(&inner),
601+ };
602+ with_animation_frame(move || match vdom.inner.upgrade() {
603+ None => {
604+ warn!("VdomWeak::render: vdom unmounted before we could render");
605+ let r = reject.call0(&JsValue::null());
606+ debug_assert!(r.is_ok());
607+ }
608+ Some(inner) => {
609+ let mut exclusive = inner.exclusive.borrow_mut();
610+ exclusive.render();
611+
612+ // We did the render, so take the promise away
613+ // and let future `render` calls request new
614+ // animation frames.
615+ let _ = inner.shared.render_scheduled.take();
616+
617+ debug!("VdomWeak::render: finished rendering");
618+ let r = resolve.call0(&JsValue::null());
619+ debug_assert!(r.is_ok());
620+ }
621+ });
622+ })
623+ });
624+ inner.shared.render_scheduled.set(Some(promise.clone()));
625+ JsFuture::from(promise)
626+ .map(|_| ())
627+ .map_err(|_| VdomDroppedError {})
628+ })
629+ }
630+}