on master help sign in
Bump-Scoped rendering and SVG class attribute fix (#136)
f482bdaAndrew J Westlake
benches/benches.rs+4−4
@@ -17,16 +17,16 @@ use std::convert::TryInto;
17
17
18/// The simplest thing we can render: `<div/>`.
18/// The simplest thing we can render: `<div/>`.
19struct Empty;
19struct Empty;
20−impl Render for Empty {
20+impl<'a> Render<'a> for Empty {
21− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
21+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
22 div(&cx).finish()
22 div(&cx).finish()
23 }
23 }
24}
24}
25
25
26/// Render a list that is `self.0` items long, has attributes and listeners.
26/// Render a list that is `self.0` items long, has attributes and listeners.
27struct SimpleList(usize);
27struct SimpleList(usize);
28−impl Render for SimpleList {
28+impl<'a> Render<'a> for SimpleList {
29− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
29+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
30 let mut children = bumpalo::collections::Vec::with_capacity_in(self.0, cx.bump);
30 let mut children = bumpalo::collections::Vec::with_capacity_in(self.0, cx.bump);
31 children.extend((0..self.0).map(|_| {
31 children.extend((0..self.0).map(|_| {
32 li(&cx)
32 li(&cx)
crates/js-api/src/lib.rs+4−4
@@ -99,8 +99,8 @@ impl GreetingViaJs {
99
99
100/// And finally the `Render` implementation! This adds a `<p>` element and some
100/// And finally the `Render` implementation! This adds a `<p>` element and some
101/// text around whatever the inner JS `Greeting` component renders.
101/// text around whatever the inner JS `Greeting` component renders.
102−impl Render for GreetingViaJs {
102+impl<'a> Render<'a> for GreetingViaJs {
103− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
103+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
104 use dodrio::builder::*;
104 use dodrio::builder::*;
105 p(&cx)
105 p(&cx)
106 .children([
106 .children([
@@ -216,8 +216,8 @@ impl JsRender {
216 }
216 }
217}
217}
218
218
219−impl Render for JsRender {
219+impl<'a> Render<'a> for JsRender {
220− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
220+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
221 create(cx, self.render())
221 create(cx, self.render())
222 }
222 }
223}
223}
examples/counter/src/lib.rs+2−2
@@ -26,8 +26,8 @@ impl Counter {
26
26
27// The `Render` implementation for `Counter`s displays the current count and has
27// The `Render` implementation for `Counter`s displays the current count and has
28// buttons to increment and decrement the count.
28// buttons to increment and decrement the count.
29−impl Render for Counter {
29+impl<'a> Render<'a> for Counter {
30− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
30+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
31 use dodrio::builder::*;
31 use dodrio::builder::*;
32
32
33 // Stringify the count as a bump-allocated string.
33 // Stringify the count as a bump-allocated string.
examples/game-of-life/src/lib.rs+2−2
@@ -126,8 +126,8 @@ impl Universe {
126}
126}
127
127
128/// The rendering implementation for our Game of Life.
128/// The rendering implementation for our Game of Life.
129−impl Render for Universe {
129+impl<'a> Render<'a> for Universe {
130− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
130+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
131 use dodrio::builder::*;
131 use dodrio::builder::*;
132
132
133 let mut rows = bumpalo::collections::Vec::with_capacity_in(self.height as usize, cx.bump);
133 let mut rows = bumpalo::collections::Vec::with_capacity_in(self.height as usize, cx.bump);
examples/hello-world/src/lib.rs+2−2
@@ -10,8 +10,8 @@ struct Hello {
10
10
11// The `Render` implementation describes how to render a `Hello` component into
11// The `Render` implementation describes how to render a `Hello` component into
12// HTML.
12// HTML.
13−impl Render for Hello {
13+impl<'a> Render<'a> for Hello {
14− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
14+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
15 let msg = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
15 let msg = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
16 let msg = msg.into_bump_str();
16 let msg = msg.into_bump_str();
17 p(&cx).children([text(msg)]).finish()
17 p(&cx).children([text(msg)]).finish()
examples/input-form/src/lib.rs+2−2
@@ -23,8 +23,8 @@ impl SayHelloTo {
23
23
24// The `Render` implementation has a text `<input>` and a `<div>` that shows a
24// The `Render` implementation has a text `<input>` and a `<div>` that shows a
25// greeting to the `<input>`'s value.
25// greeting to the `<input>`'s value.
26−impl Render for SayHelloTo {
26+impl<'a> Render<'a> for SayHelloTo {
27− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
27+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
28 use dodrio::builder::*;
28 use dodrio::builder::*;
29
29
30 div(&cx)
30 div(&cx)
examples/js-component/src/lib.rs+2−2
@@ -31,8 +31,8 @@ impl GreetingViaJs {
31
31
32/// Here's the `Render` implementation! This adds a `<p>` element and some text
32/// Here's the `Render` implementation! This adds a `<p>` element and some text
33/// around whatever the inner JS `Greeting` component renders.
33/// around whatever the inner JS `Greeting` component renders.
34−impl Render for GreetingViaJs {
34+impl<'a> Render<'a> for GreetingViaJs {
35− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
35+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
36 use dodrio::builder::*;
36 use dodrio::builder::*;
37 p(&cx)
37 p(&cx)
38 .children([text("JavaScript says: "), self.js.render(cx)])
38 .children([text("JavaScript says: "), self.js.render(cx)])
examples/moire/src/lib.rs+2−2
@@ -104,8 +104,8 @@ impl Moire {
104 }
104 }
105}
105}
106
106
107−impl Render for Moire {
107+impl<'a> Render<'a> for Moire {
108− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
108+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
109 use dodrio::builder::*;
109 use dodrio::builder::*;
110
110
111 let elapsed = web_sys::window()
111 let elapsed = web_sys::window()
examples/sierpinski-triangle/src/lib.rs+2−2
@@ -85,8 +85,8 @@ impl Container {
85 }
85 }
86}
86}
87
87
88−impl Render for Container {
88+impl<'a> Render<'a> for Container {
89− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
89+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
90 use dodrio::builder::div;
90 use dodrio::builder::div;
91
91
92 let elapsed = web_sys::window()
92 let elapsed = web_sys::window()
examples/todomvc/src/todo.rs+2−2
@@ -116,8 +116,8 @@ impl<C> Todo<C> {
116 }
116 }
117}
117}
118
118
119−impl<C: TodoActions> Render for Todo<C> {
119+impl<'a, C: TodoActions> Render<'a> for Todo<C> {
120− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
120+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
121 use dodrio::{
121 use dodrio::{
122 builder::*,
122 builder::*,
123 bumpalo::{self, collections::String},
123 bumpalo::{self, collections::String},
examples/todomvc/src/todos.rs+2−2
@@ -289,8 +289,8 @@ impl<C: TodosActions> Todos<C> {
289 }
289 }
290}
290}
291
291
292−impl<C: TodosActions> Render for Todos<C> {
292+impl<'a, C: TodosActions> Render<'a> for Todos<C> {
293− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
293+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
294 use dodrio::builder::*;
294 use dodrio::builder::*;
295
295
296 div(&cx)
296 div(&cx)
src/cached.rs+7−7
@@ -45,8 +45,8 @@ where
45 /// count: u32,
45 /// count: u32,
46 /// }
46 /// }
47 ///
47 ///
48− /// impl Render for Counter {
48+ /// impl<'a> Render<'a> for Counter {
49− /// fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
49+ /// fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
50 /// // ...
50 /// // ...
51 /// # unimplemented!()
51 /// # unimplemented!()
52 /// }
52 /// }
@@ -85,8 +85,8 @@ where
85 /// who: String
85 /// who: String
86 /// }
86 /// }
87 ///
87 ///
88− /// impl Render for Hello {
88+ /// impl<'a> Render<'a> for Hello {
89− /// fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
89+ /// fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
90 /// use dodrio::builder::*;
90 /// use dodrio::builder::*;
91 /// let greeting = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
91 /// let greeting = bumpalo::format!(in cx.bump, "Hello, {}!", self.who);
92 /// p(&cx)
92 /// p(&cx)
@@ -143,11 +143,11 @@ where
143 }
143 }
144}
144}
145
145
146−impl<R> Render for Cached<R>
146+impl<'a, R> Render<'a> for Cached<R>
147where
147where
148− R: 'static + Default + Render,
148+ R: 'static + Default + for<'b> Render<'b>,
149{
149{
150− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
150+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
151 let template = cx.template::<R>();
151 let template = cx.template::<R>();
152 let cached = match self.cached.get() {
152 let cached = match self.cached.get() {
153 // This does-the-cache-contain-this-id check is necessary because
153 // This does-the-cache-contain-this-id check is necessary because
src/change_list/mod.rs+2−2
@@ -233,9 +233,9 @@ impl ChangeListBuilder<'_> {
233 self.state.emitter.replace_with();
233 self.state.emitter.replace_with();
234 }
234 }
235
235
236− pub fn set_attribute(&mut self, name: &str, value: &str) {
236+ pub fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool) {
237 debug_assert!(self.traversal_is_committed());
237 debug_assert!(self.traversal_is_committed());
238− if name == "class" {
238+ if name == "class" && !is_namespaced {
239 let class_id = self.ensure_string(value);
239 let class_id = self.ensure_string(value);
240 debug!("emit: set_class({:?})", value);
240 debug!("emit: set_class({:?})", value);
241 self.state.emitter.set_class(class_id.into());
241 self.state.emitter.set_class(class_id.into());
src/diff.rs+6−6
@@ -78,7 +78,7 @@ pub(crate) fn diff(
78 return;
78 return;
79 }
79 }
80 diff_listeners(change_list, registry, old_listeners, new_listeners);
80 diff_listeners(change_list, registry, old_listeners, new_listeners);
81− diff_attributes(change_list, old_attributes, new_attributes);
81+ diff_attributes(change_list, old_attributes, new_attributes, new_namespace.is_some());
82 diff_children(
82 diff_children(
83 cached_set,
83 cached_set,
84 change_list,
84 change_list,
@@ -204,26 +204,26 @@ fn diff_listeners(
204// [... node]
204// [... node]
205//
205//
206// The change list stack is left unchanged.
206// The change list stack is left unchanged.
207−fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new: &[Attribute]) {
207+fn diff_attributes(change_list: &mut ChangeListBuilder, old: &[Attribute], new: &[Attribute], is_namespaced: bool) {
208 // Do O(n^2) passes to add/update and remove attributes, since
208 // Do O(n^2) passes to add/update and remove attributes, since
209 // there are almost always very few attributes.
209 // there are almost always very few attributes.
210 'outer: for new_attr in new {
210 'outer: for new_attr in new {
211 if new_attr.is_volatile() {
211 if new_attr.is_volatile() {
212 change_list.commit_traversal();
212 change_list.commit_traversal();
213− change_list.set_attribute(new_attr.name, new_attr.value);
213+ change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
214 } else {
214 } else {
215 for old_attr in old {
215 for old_attr in old {
216 if old_attr.name == new_attr.name {
216 if old_attr.name == new_attr.name {
217 if old_attr.value != new_attr.value {
217 if old_attr.value != new_attr.value {
218 change_list.commit_traversal();
218 change_list.commit_traversal();
219− change_list.set_attribute(new_attr.name, new_attr.value);
219+ change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
220 }
220 }
221 continue 'outer;
221 continue 'outer;
222 }
222 }
223 }
223 }
224
224
225 change_list.commit_traversal();
225 change_list.commit_traversal();
226− change_list.set_attribute(new_attr.name, new_attr.value);
226+ change_list.set_attribute(new_attr.name, new_attr.value, is_namespaced);
227 }
227 }
228 }
228 }
229
229
@@ -953,7 +953,7 @@ fn create(
953 }
953 }
954
954
955 for attr in attributes {
955 for attr in attributes {
956− change_list.set_attribute(&attr.name, &attr.value);
956+ change_list.set_attribute(&attr.name, &attr.value, namespace.is_some());
957 }
957 }
958
958
959 // Fast path: if there is a single text child, it is faster to
959 // Fast path: if there is a single text child, it is faster to
src/lib.rs+2−2
@@ -18,8 +18,8 @@
18//! }
18//! }
19//! }
19//! }
20//!
20//!
21−//! impl<'who> Render for Hello<'who> {
21+//! impl<'a, 'who> Render<'a> for Hello<'who> {
22−//! fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
22+//! fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
23//! use dodrio::builder::*;
23//! use dodrio::builder::*;
24//!
24//!
25//! let id = bumpalo::format!(in cx.bump, "hello-{}", self.who);
25//! let id = bumpalo::format!(in cx.bump, "hello-{}", self.who);
src/render.rs+39−12
@@ -21,8 +21,8 @@ use wasm_bindgen::UnwrapThrowExt;
21///
21///
22/// pub struct MyComponent;
22/// pub struct MyComponent;
23///
23///
24−/// impl Render for MyComponent {
24+/// impl<'a> Render<'a> for MyComponent {
25−/// fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
25+/// fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
26/// use dodrio::builder::*;
26/// use dodrio::builder::*;
27///
27///
28/// p(&cx)
28/// p(&cx)
@@ -35,26 +35,26 @@ use wasm_bindgen::UnwrapThrowExt;
35/// }
35/// }
36/// }
36/// }
37/// ```
37/// ```
38−pub trait Render {
38+pub trait Render<'a> {
39 /// Render `self` as a virtual DOM. Use the given context's `Bump` for
39 /// Render `self` as a virtual DOM. Use the given context's `Bump` for
40 /// temporary allocations.
40 /// temporary allocations.
41− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a>;
41+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a>;
42}
42}
43
43
44−impl<'r, R> Render for &'r R
44+impl<'a, 'r, R> Render<'a> for &'r R
45where
45where
46− R: Render,
46+ R: Render<'a>,
47{
47{
48− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
48+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
49 (**self).render(cx)
49 (**self).render(cx)
50 }
50 }
51}
51}
52
52
53−impl<R> Render for Rc<R>
53+impl<'a, R> Render<'a> for Rc<R>
54where
54where
55− R: Render,
55+ R: Render<'a>,
56{
56{
57− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
57+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
58 (**self).render(cx)
58 (**self).render(cx)
59 }
59 }
60}
60}
@@ -69,7 +69,7 @@ where
69/// You do not need to implement this trait by hand: there is a blanket
69/// You do not need to implement this trait by hand: there is a blanket
70/// implementation for all `Render` types that fulfill the `RootRender`
70/// implementation for all `Render` types that fulfill the `RootRender`
71/// requirements.
71/// requirements.
72−pub trait RootRender: Any + Render {
72+pub trait RootRender: Any + for<'a> Render<'a> {
73 /// Get this `&RootRender` trait object as an `&Any` trait object reference.
73 /// Get this `&RootRender` trait object as an `&Any` trait object reference.
74 fn as_any(&self) -> &dyn Any;
74 fn as_any(&self) -> &dyn Any;
75
75
@@ -80,7 +80,7 @@ pub trait RootRender: Any + Render {
80
80
81impl<T> RootRender for T
81impl<T> RootRender for T
82where
82where
83− T: Any + Render,
83+ T: Any + for<'a> Render<'a>,
84{
84{
85 fn as_any(&self) -> &dyn Any {
85 fn as_any(&self) -> &dyn Any {
86 self
86 self
@@ -138,4 +138,31 @@ mod tests {
138 #[allow(dead_code)]
138 #[allow(dead_code)]
139 fn takes_dyn_render(_: &dyn super::RootRender) {}
139 fn takes_dyn_render(_: &dyn super::RootRender) {}
140 }
140 }
141+
142+ #[test]
143+ fn render_bump_scoped_child() {
144+ use crate::{builder::*, bumpalo::collections::String, Node, Render, RenderContext};
145+
146+ struct Child<'a> {
147+ name: &'a str,
148+ }
149+
150+ impl<'a> Render<'a> for Child<'a> {
151+ fn render(&self, _cx: &mut RenderContext<'a>) -> Node<'a> {
152+ text(self.name)
153+ }
154+ }
155+
156+ struct Parent;
157+
158+ impl<'a> Render<'a> for Parent {
159+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
160+ let child_name = String::from_str_in("child", cx.bump).into_bump_str();
161+
162+ div(&cx)
163+ .children([Child { name: child_name }.render(cx)])
164+ .finish()
165+ }
166+ }
167+ }
141}
168}
src/render_context.rs+1−1
@@ -69,7 +69,7 @@ impl<'a> RenderContext<'a> {
69 /// Get or create the cached template for `Cached<R>`.
69 /// Get or create the cached template for `Cached<R>`.
70 pub(crate) fn template<R>(&mut self) -> Option<CacheId>
70 pub(crate) fn template<R>(&mut self) -> Option<CacheId>
71 where
71 where
72− R: 'static + Default + Render,
72+ R: 'static + Default + for<'b> Render<'b>,
73 {
73 {
74 let template_id = Cached::<R>::template_id();
74 let template_id = Cached::<R>::template_id();
75 if let Some(cache_id) = self.templates.get(&template_id).cloned() {
75 if let Some(cache_id) = self.templates.get(&template_id).cloned() {
tests/web/cached.rs+4−4
@@ -18,8 +18,8 @@ impl CountRenders {
18 }
18 }
19}
19}
20
20
21−impl Render for CountRenders {
21+impl<'a> Render<'a> for CountRenders {
22− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
22+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
23 let count = self.render_count.get() + 1;
23 let count = self.render_count.get() + 1;
24 self.render_count.set(count);
24 self.render_count.set(count);
25
25
@@ -153,8 +153,8 @@ impl Default for Id {
153 }
153 }
154}
154}
155
155
156−impl Render for Id {
156+impl<'a> Render<'a> for Id {
157− fn render<'a>(&self, _cx: &mut RenderContext<'a>) -> Node<'a> {
157+ fn render(&self, _cx: &mut RenderContext<'a>) -> Node<'a> {
158 text(self.0)
158 text(self.0)
159 }
159 }
160}
160}
tests/web/events.rs+4−4
@@ -24,8 +24,8 @@ impl EventContainer {
24 }
24 }
25}
25}
26
26
27−impl Render for EventContainer {
27+impl<'a> Render<'a> for EventContainer {
28− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
28+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
29 use dodrio::builder::*;
29 use dodrio::builder::*;
30 div(&cx)
30 div(&cx)
31 .attr("id", "target")
31 .attr("id", "target")
@@ -126,8 +126,8 @@ impl ListensOnlyOnFirstRender {
126 }
126 }
127}
127}
128
128
129−impl Render for ListensOnlyOnFirstRender {
129+impl<'a> Render<'a> for ListensOnlyOnFirstRender {
130− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
130+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
131 use dodrio::builder::*;
131 use dodrio::builder::*;
132
132
133 let count = self.count.get();
133 let count = self.count.get();
tests/web/js_api.rs+2−2
@@ -35,8 +35,8 @@ impl WrapJs {
35 }
35 }
36}
36}
37
37
38−impl Render for WrapJs {
38+impl<'a> Render<'a> for WrapJs {
39− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
39+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
40 div(&cx)
40 div(&cx)
41 .attr("class", "wrap-js")
41 .attr("class", "wrap-js")
42 .children([self.inner.render(cx)])
42 .children([self.inner.render(cx)])
tests/web/keyed.rs+4−4
@@ -7,8 +7,8 @@ use wasm_bindgen_test::*;
7
7
8struct Keyed(u16);
8struct Keyed(u16);
9
9
10−impl Render for Keyed {
10+impl<'a> Render<'a> for Keyed {
11− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
11+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
12 let key = bumpalo::format!(in cx.bump, "{}", self.0).into_bump_str();
12 let key = bumpalo::format!(in cx.bump, "{}", self.0).into_bump_str();
13 div(&cx)
13 div(&cx)
14 .attr("class", "keyed")
14 .attr("class", "keyed")
@@ -33,8 +33,8 @@ where
33
33
34async fn assert_keyed<Before, After>(before: Before, after: After) -> Result<(), JsValue>
34async fn assert_keyed<Before, After>(before: Before, after: After) -> Result<(), JsValue>
35where
35where
36− Before: 'static + Render,
36+ Before: 'static + for<'a> Render<'a>,
37− After: 'static + Render,
37+ After: 'static + for<'a> Render<'a>,
38{
38{
39 #[wasm_bindgen(module = "/tests/web/keyed.js")]
39 #[wasm_bindgen(module = "/tests/web/keyed.js")]
40 extern "C" {
40 extern "C" {
tests/web/main.rs+6−6
@@ -50,7 +50,7 @@ pub fn init_logging() {
50
50
51/// Assert that the `container` contains the physical DOM tree that matches
51/// Assert that the `container` contains the physical DOM tree that matches
52/// `r`'s rendered virtual DOM.
52/// `r`'s rendered virtual DOM.
53−pub fn assert_rendered<R: Render>(container: &web_sys::Element, r: &R) {
53+pub fn assert_rendered<R: for<'a> Render<'a>>(container: &web_sys::Element, r: &R) {
54 init_logging();
54 init_logging();
55
55
56 let cached_set = &RefCell::new(CachedSet::default());
56 let cached_set = &RefCell::new(CachedSet::default());
@@ -155,11 +155,11 @@ pub struct RenderFn<F>(F)
155where
155where
156 F: for<'a> Fn(&mut RenderContext<'a>) -> Node<'a>;
156 F: for<'a> Fn(&mut RenderContext<'a>) -> Node<'a>;
157
157
158−impl<F> Render for RenderFn<F>
158+impl<'a, F> Render<'a> for RenderFn<F>
159where
159where
160− F: for<'a> Fn(&mut RenderContext<'a>) -> Node<'a>,
160+ F: for<'b> Fn(&mut RenderContext<'b>) -> Node<'b>,
161{
161{
162− fn render<'a>(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
162+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
163 (self.0)(cx)
163 (self.0)(cx)
164 }
164 }
165}
165}
@@ -169,8 +169,8 @@ where
169/// the physical DOM tree correctly matches `after`.
169/// the physical DOM tree correctly matches `after`.
170pub async fn assert_before_after<R, S>(before: R, after: S) -> Result<(), JsValue>
170pub async fn assert_before_after<R, S>(before: R, after: S) -> Result<(), JsValue>
171where
171where
172− R: 'static + Render,
172+ R: 'static + for<'a> Render<'a>,
173− S: 'static + Render,
173+ S: 'static + for<'a> Render<'a>,
174{
174{
175 let container = create_element("div");
175 let container = create_element("div");
176
176
tests/web/render.rs+89−1
@@ -1,6 +1,7 @@
1use super::{assert_rendered, before_after, create_element, RenderFn};
1use super::{assert_rendered, before_after, create_element, RenderFn};
2−use dodrio::{builder::*, Vdom};
2+use dodrio::{builder::*, bumpalo::collections::String, Node, Render, RenderContext, Vdom};
3use std::rc::Rc;
3use std::rc::Rc;
4+use wasm_bindgen::{ JsCast};
4use wasm_bindgen_test::*;
5use wasm_bindgen_test::*;
5
6
6#[wasm_bindgen_test]
7#[wasm_bindgen_test]
@@ -34,6 +35,93 @@ fn container_is_emptied_upon_drop() {
34 assert!(container.first_child().is_none());
35 assert!(container.first_child().is_none());
35}
36}
36
37
38+/// Renders a child with a lifetime scoped to the RenderContext bump arena.
39+#[wasm_bindgen_test]
40+fn render_bump_scoped_node() {
41+ struct Child<'a> {
42+ name: &'a str,
43+ }
44+
45+ impl<'a> Render<'a> for Child<'a> {
46+ fn render(&self, _cx: &mut RenderContext<'a>) -> Node<'a> {
47+ text(self.name)
48+ }
49+ }
50+
51+ struct Parent;
52+
53+ impl<'a> Render<'a> for Parent {
54+ fn render(&self, cx: &mut RenderContext<'a>) -> Node<'a> {
55+ let child_name = String::from_str_in("child", cx.bump).into_bump_str();
56+
57+ div(&cx)
58+ .children([Child { name: child_name }.render(cx)])
59+ .finish()
60+ }
61+ }
62+
63+ let parent = Rc::new(RenderFn(|cx| {
64+ Parent.render(cx)
65+ }));
66+
67+ let container = create_element("div");
68+ let _vdom = Vdom::new(&container, parent.clone());
69+
70+ assert_rendered(&container, &parent);
71+}
72+
73+/// Originally, dodrio would use the className property for SVGs.
74+///
75+/// This is problematic because when SVG elements are created, the className is flagged as a read
76+/// only property, so setting it causes an exception to be thrown. Here's an example of how this
77+/// happens:
78+///
79+/// let elem = web_sys::window()
80+/// .unwrap()
81+/// .document()
82+/// .unwrap()
83+/// .create_element_ns(Some("http://www.w3.org/2000/svg"), "svg")
84+/// .unwrap();
85+///
86+/// elem.set_class_name("does-not-work");
87+///
88+/// -----------------------------------------------------------------------------------------------
89+///
90+/// wasm-bindgen: imported JS function that was not marked as `catch` threw an error:
91+/// setting getter-only property "className"
92+///
93+/// -----------------------------------------------------------------------------------------------
94+///
95+/// Now, dodrio passes the 'class' attribute of all namespaced elements into set_attribute. This
96+/// satisfies the restrictions on SVG and keeps the optimized path for non-namespaced elements
97+#[wasm_bindgen_test(async)]
98+async fn test_svg_set_class() {
99+ let container = create_element("div");
100+
101+
102+ let valid_svg = Rc::new(RenderFn(|cx| {
103+ ElementBuilder::new(cx.bump, "svg")
104+ .namespace(Some("http://www.w3.org/2000/svg"))
105+ .attr("class", "works")
106+ .finish()
107+ }));
108+
109+ let vdom = Vdom::new(&container, valid_svg.clone());
110+ let weak = vdom.weak();
111+
112+ weak.render().await.unwrap();
113+
114+ assert_eq!(
115+ "works",
116+ container.first_child()
117+ .expect("unable to get svg")
118+ .dyn_ref::<web_sys::Element>()
119+ .expect("svg should be an element")
120+ .get_attribute("class")
121+ .expect("unable to get 'class' of svg")
122+ );
123+}
124+
37before_after! {
125before_after! {
38 same_text {
126 same_text {
39 before(_cx) {
127 before(_cx) {