on master help sign in
Merge pull request #41 from rylev/svg-support
0c2d5f0Nick Fitzgerald
.gitignore+1
@@ -1,3 +1,4 @@
1/target
1/target
2**/*.rs.bk
2**/*.rs.bk
3wasm-pack.log
3wasm-pack.log
4+/.vscode
js/change-list.js+21
@@ -175,6 +175,27 @@ const OP_TABLE = [
175 const id = mem32[i++];
175 const id = mem32[i++];
176 changeList.dropString(id);
176 changeList.dropString(id);
177 return i;
177 return i;
178+ },
179+
180+ // 16
181+ function createElementNS(changeList, mem8, mem32, i) {
182+ const tagNameId = mem32[i++];
183+ const tagName = changeList.getString(tagNameId);
184+ const nsId = mem32[i++];
185+ const ns = changeList.getString(nsId);
186+ changeList.stack.push(document.createElementNS(ns, tagName));
187+ return i;
188+ },
189+
190+ // 17
191+ function setAttributeNS(changeList, mem8, mem32, i) {
192+ const nameId = mem32[i++];
193+ const valueId = mem32[i++];
194+ const name = changeList.getString(nameId);
195+ const value = changeList.getString(valueId);
196+ const node = top(changeList.stack);
197+ node.setAttributeNS(null, name, value);
198+ return i;
178 }
199 }
179];
200];
180
201
src/builder.rs+89
@@ -20,6 +20,7 @@ where
20 listeners: Listeners,
20 listeners: Listeners,
21 attributes: Attributes,
21 attributes: Attributes,
22 children: Children,
22 children: Children,
23+ namespace: Option<&'a str>,
23}
24}
24
25
25impl<'a>
26impl<'a>
@@ -60,6 +61,7 @@ impl<'a>
60 listeners: bumpalo::collections::Vec::new_in(bump),
61 listeners: bumpalo::collections::Vec::new_in(bump),
61 attributes: bumpalo::collections::Vec::new_in(bump),
62 attributes: bumpalo::collections::Vec::new_in(bump),
62 children: bumpalo::collections::Vec::new_in(bump),
63 children: bumpalo::collections::Vec::new_in(bump),
64+ namespace: None,
63 }
65 }
64 }
66 }
65}
67}
@@ -108,6 +110,7 @@ where
108 listeners,
110 listeners,
109 attributes: self.attributes,
111 attributes: self.attributes,
110 children: self.children,
112 children: self.children,
113+ namespace: self.namespace,
111 }
114 }
112 }
115 }
113
116
@@ -145,6 +148,7 @@ where
145 listeners: self.listeners,
148 listeners: self.listeners,
146 attributes,
149 attributes,
147 children: self.children,
150 children: self.children,
151+ namespace: self.namespace,
148 }
152 }
149 }
153 }
150
154
@@ -182,6 +186,33 @@ where
182 listeners: self.listeners,
186 listeners: self.listeners,
183 attributes: self.attributes,
187 attributes: self.attributes,
184 children,
188 children,
189+ namespace: self.namespace,
190+ }
191+ }
192+
193+ /// Set the namespace for this element.
194+ ///
195+ /// # Example
196+ ///
197+ /// ```no_run
198+ /// use dodrio::{builder::*, bumpalo::Bump};
199+ ///
200+ /// let b = Bump::new();
201+ ///
202+ /// // Create a `<td>` tag with an xhtml namespace
203+ /// let my_div = td(&b)
204+ /// .namespace(Some("http://www.w3.org/1999/xhtml"))
205+ /// .finish();
206+ /// ```
207+ #[inline]
208+ pub fn namespace(self, namespace: Option<&'a str>) -> Self {
209+ ElementBuilder {
210+ bump: self.bump,
211+ tag_name: self.tag_name,
212+ listeners: self.listeners,
213+ attributes: self.attributes,
214+ children: self.children,
215+ namespace,
185 }
216 }
186 }
217 }
187
218
@@ -208,6 +239,7 @@ where
208 self.listeners,
239 self.listeners,
209 self.attributes,
240 self.attributes,
210 self.children,
241 self.children,
242+ self.namespace,
211 )
243 )
212 }
244 }
213}
245}
@@ -357,6 +389,26 @@ macro_rules! builder_constructors {
357 ElementBuilder::new(bump, stringify!($name))
389 ElementBuilder::new(bump, stringify!($name))
358 }
390 }
359 )*
391 )*
392+ };
393+ ( $(
394+ $(#[$attr:meta])*
395+ $name:ident <> $namespace:tt;
396+ )* ) => {
397+ $(
398+ $(#[$attr])*
399+ #[inline]
400+ pub fn $name<'a>(
401+ bump: &'a Bump,
402+ ) -> ElementBuilder<
403+ 'a,
404+ bumpalo::collections::Vec<'a, Listener<'a>>,
405+ bumpalo::collections::Vec<'a, Attribute<'a>>,
406+ bumpalo::collections::Vec<'a, Node<'a>>,
407+ > {
408+ let builder = ElementBuilder::new(bump, stringify!($name));
409+ builder.namespace(Some($namespace))
410+ }
411+ )*
360 }
412 }
361}
413}
362
414
@@ -861,6 +913,43 @@ builder_constructors! {
861 template;
913 template;
862}
914}
863
915
916+builder_constructors! {
917+ // SVG components
918+
919+ /// Build a
920+ /// [`<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)
921+ /// element.
922+ svg <> "http://www.w3.org/2000/svg" ;
923+ /// Build a
924+ /// [`<path>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)
925+ /// element.
926+ path <> "http://www.w3.org/2000/svg";
927+ /// Build a
928+ /// [`<circle>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle)
929+ /// element.
930+ circle <> "http://www.w3.org/2000/svg";
931+ /// Build a
932+ /// [`<ellipse>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse)
933+ /// element.
934+ ellipse <> "http://www.w3.org/2000/svg";
935+ /// Build a
936+ /// [`<line>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line)
937+ /// element.
938+ line <> "http://www.w3.org/2000/svg";
939+ /// Build a
940+ /// [`<polygon>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polygon)
941+ /// element.
942+ polygon <> "http://www.w3.org/2000/svg";
943+ /// Build a
944+ /// [`<polyline>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polyline)
945+ /// element.
946+ polyline <> "http://www.w3.org/2000/svg";
947+ /// Build a
948+ /// [`<rect>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect)
949+ /// element.
950+ rect <> "http://www.w3.org/2000/svg";
951+}
952+
864/// Construct a text node.
953/// Construct a text node.
865///
954///
866/// This is `dodrio`'s virtual DOM equivalent of `document.createTextNode`.
955/// This is `dodrio`'s virtual DOM equivalent of `document.createTextNode`.
src/change_list.rs+32
@@ -267,6 +267,24 @@ enum ChangeDiscriminant {
267 /// dropString(id);
267 /// dropString(id);
268 /// ```
268 /// ```
269 DropString = 15,
269 DropString = 15,
270+
271+ /// Immediates: `(pointer1, length1, pointer2, length2)`
272+ ///
273+ /// Stack: `[...] -> [... Node]`
274+ ///
275+ /// ```text
276+ /// stack.push(createElementNS(String(pointer1, length1), String(pointer2, length2))
277+ /// ```
278+ CreateElementNS = 16,
279+
280+ /// Immediates: `(pointer1, length1, pointer2, length2, pointer3, length3)`
281+ ///
282+ /// Stack: `[... Node] -> [... Node]`
283+ ///
284+ /// ```text
285+ /// stack.top().setAttributeNS(String(pointer1, length1), String(pointer2, length2), String(pointer3, length3))
286+ /// ```
287+ SetAttributeNS = 17,
270}
288}
271
289
272// Allocation utilities to ensure that we only allocation sequences of `u32`s
290// Allocation utilities to ensure that we only allocation sequences of `u32`s
@@ -361,6 +379,13 @@ impl ChangeList {
361 self.op2(ChangeDiscriminant::SetAttribute, name_id, value_id);
379 self.op2(ChangeDiscriminant::SetAttribute, name_id, value_id);
362 }
380 }
363
381
382+ pub(crate) fn emit_set_attribute_ns(&mut self, name: &str, value: &str) {
383+ debug!("emit_set_attribute_ns({:?}, {:?}", name, value);
384+ let name_id = self.ensure_string(name);
385+ let value_id = self.ensure_string(value);
386+ self.op2(ChangeDiscriminant::SetAttributeNS, name_id, value_id);
387+ }
388+
364 pub(crate) fn emit_remove_attribute(&mut self, name: &str) {
389 pub(crate) fn emit_remove_attribute(&mut self, name: &str) {
365 debug!("emit_remove_attribute({:?})", name);
390 debug!("emit_remove_attribute({:?})", name);
366 let name_id = self.ensure_string(name);
391 let name_id = self.ensure_string(name);
@@ -402,6 +427,13 @@ impl ChangeList {
402 self.op1(ChangeDiscriminant::CreateElement, tag_name_id);
427 self.op1(ChangeDiscriminant::CreateElement, tag_name_id);
403 }
428 }
404
429
430+ pub(crate) fn emit_create_element_ns(&mut self, tag_name: &str, ns: &str) {
431+ debug!("emit_create_element_ns({:?}, {:?})", tag_name, ns);
432+ let tag_name_id = self.ensure_string(tag_name);
433+ let ns_id = self.ensure_string(ns);
434+ self.op2(ChangeDiscriminant::CreateElementNS, tag_name_id, ns_id);
435+ }
436+
405 pub(crate) fn emit_new_event_listener(&mut self, listener: &Listener) {
437 pub(crate) fn emit_new_event_listener(&mut self, listener: &Listener) {
406 debug!("emit_new_event_listener({:?})", listener);
438 debug!("emit_new_event_listener({:?})", listener);
407 let (a, b) = listener.get_callback_parts();
439 let (a, b) = listener.get_callback_parts();
src/node.rs+9−1
@@ -29,6 +29,7 @@ pub struct ElementNode<'a> {
29 pub(crate) listeners: &'a [Listener<'a>],
29 pub(crate) listeners: &'a [Listener<'a>],
30 pub(crate) attributes: &'a [Attribute<'a>],
30 pub(crate) attributes: &'a [Attribute<'a>],
31 pub(crate) children: &'a [Node<'a>],
31 pub(crate) children: &'a [Node<'a>],
32+ pub(crate) namespace: Option<&'a str>,
32}
33}
33
34
34/// An event listener callback function.
35/// An event listener callback function.
@@ -96,7 +97,7 @@ impl<'a> Attribute<'a> {
96}
97}
97
98
98impl<'a> Node<'a> {
99impl<'a> Node<'a> {
99− /// Construct a new element node with the given tag name and children.
100+ /// Construct a new Node of type element with given tag name and children
100 #[inline]
101 #[inline]
101 pub(crate) fn element<Listeners, Attributes, Children>(
102 pub(crate) fn element<Listeners, Attributes, Children>(
102 bump: &'a Bump,
103 bump: &'a Bump,
@@ -104,6 +105,7 @@ impl<'a> Node<'a> {
104 listeners: Listeners,
105 listeners: Listeners,
105 attributes: Attributes,
106 attributes: Attributes,
106 children: Children,
107 children: Children,
108+ namespace: Option<&'a str>,
107 ) -> Node<'a>
109 ) -> Node<'a>
108 where
110 where
109 Listeners: 'a + AsRef<[Listener<'a>]>,
111 Listeners: 'a + AsRef<[Listener<'a>]>,
@@ -124,6 +126,7 @@ impl<'a> Node<'a> {
124 listeners,
126 listeners,
125 attributes,
127 attributes,
126 children,
128 children,
129+ namespace,
127 })
130 })
128 }
131 }
129
132
@@ -188,6 +191,11 @@ impl<'a> ElementNode<'a> {
188 pub fn children(&self) -> &'a [Node<'a>] {
191 pub fn children(&self) -> &'a [Node<'a>] {
189 self.children
192 self.children
190 }
193 }
194+
195+ /// Get this element's namespace.
196+ pub fn namespace(&self) -> Option<&'a str> {
197+ self.namespace
198+ }
191}
199}
192
200
193union CallbackFatPtr<'a> {
201union CallbackFatPtr<'a> {
src/vdom.rs+19−7
@@ -138,7 +138,7 @@ impl Vdom {
138 container.set_inner_html("");
138 container.set_inner_html("");
139
139
140 // Create a dummy `<div/>` in our container.
140 // Create a dummy `<div/>` in our container.
141− let current_root = Node::element(&dom_buffers[0], "div", [], [], []);
141+ let current_root = Node::element(&dom_buffers[0], "div", [], [], [], None);
142 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
142 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
143 let window = web_sys::window().expect("should have access to the Window");
143 let window = web_sys::window().expect("should have access to the Window");
144 let document = window
144 let document = window
@@ -287,12 +287,12 @@ impl VdomInnerExclusive {
287 self.change_list.emit_set_text(new_text);
287 self.change_list.emit_set_text(new_text);
288 }
288 }
289 }
289 }
290− (&Node::Text(TextNode { .. }), Node::Element(ElementNode { .. })) => {
290+ (&Node::Text(_), Node::Element(_)) => {
291 debug!(" replacing a text node with an element");
291 debug!(" replacing a text node with an element");
292 self.create(registry, new);
292 self.create(registry, new);
293 self.change_list.emit_replace_with();
293 self.change_list.emit_replace_with();
294 }
294 }
295− (&Node::Element(ElementNode { .. }), Node::Text(TextNode { .. })) => {
295+ (&Node::Element(_), Node::Text(_)) => {
296 debug!(" replacing an element with a text node");
296 debug!(" replacing an element with a text node");
297 self.create(registry, new);
297 self.create(registry, new);
298 self.change_list.emit_replace_with();
298 self.change_list.emit_replace_with();
@@ -303,17 +303,19 @@ impl VdomInnerExclusive {
303 listeners: new_listeners,
303 listeners: new_listeners,
304 attributes: new_attributes,
304 attributes: new_attributes,
305 children: new_children,
305 children: new_children,
306+ namespace: new_namespace,
306 }),
307 }),
307 Node::Element(ElementNode {
308 Node::Element(ElementNode {
308 tag_name: old_tag_name,
309 tag_name: old_tag_name,
309 listeners: old_listeners,
310 listeners: old_listeners,
310 attributes: old_attributes,
311 attributes: old_attributes,
311 children: old_children,
312 children: old_children,
313+ namespace: old_namespace,
312 }),
314 }),
313 ) => {
315 ) => {
314 debug!(" updating an element");
316 debug!(" updating an element");
315− if new_tag_name != old_tag_name {
317+ if new_tag_name != old_tag_name || new_namespace != old_namespace {
316− debug!(" different tag names; creating new element and replacing old element");
318+ debug!(" different tag names or namespaces; creating new element and replacing old element");
317 self.create(registry, new);
319 self.create(registry, new);
318 self.change_list.emit_replace_with();
320 self.change_list.emit_replace_with();
319 return;
321 return;
@@ -462,8 +464,13 @@ impl VdomInnerExclusive {
462 listeners,
464 listeners,
463 attributes,
465 attributes,
464 children,
466 children,
467+ namespace,
465 }) => {
468 }) => {
469+ if let Some(namespace) = namespace {
470+ self.change_list.emit_create_element_ns(tag_name, namespace);
471+ } else {
466− self.change_list.emit_create_element(tag_name);
472+ self.change_list.emit_create_element(tag_name);
473+ }
467 for l in listeners {
474 for l in listeners {
468 unsafe {
475 unsafe {
469 registry.add(l);
476 registry.add(l);
@@ -471,7 +478,12 @@ impl VdomInnerExclusive {
471 self.change_list.emit_new_event_listener(l);
478 self.change_list.emit_new_event_listener(l);
472 }
479 }
473 for attr in attributes {
480 for attr in attributes {
481+ if namespace.is_none() || attr.name.starts_with("xmlns") {
474− self.change_list.emit_set_attribute(&attr.name, &attr.value);
482+ self.change_list.emit_set_attribute(&attr.name, &attr.value);
483+ } else {
484+ self.change_list
485+ .emit_set_attribute_ns(&attr.name, &attr.value);
486+ }
475 }
487 }
476 for child in children {
488 for child in children {
477 self.create(registry, child.clone());
489 self.create(registry, child.clone());
tests/web/main.rs+3
@@ -92,6 +92,9 @@ pub fn assert_rendered<R: Render>(container: &web_sys::Element, r: &R) {
92 .expect("`actual` should be an `Element`");
92 .expect("`actual` should be an `Element`");
93 check_attributes(actual.attributes(), elem.attributes());
93 check_attributes(actual.attributes(), elem.attributes());
94 check_children(actual.child_nodes(), elem.children());
94 check_children(actual.child_nodes(), elem.children());
95+ if let Some(namespace) = elem.namespace() {
96+ assert_eq!(actual.namespace_uri(), Some(namespace.into()))
97+ }
95 }
98 }
96 }
99 }
97 }
100 }
tests/web/render.rs+13
@@ -240,4 +240,17 @@ before_after! {
240 .finish()
240 .finish()
241 }
241 }
242 }
242 }
243+
244+ same_tag_different_namespace {
245+ before(bump) {
246+ div(bump)
247+ .namespace(Some("http://example.com"))
248+ .finish()
249+ }
250+ after(bump) {
251+ div(bump)
252+ .namespace(Some("http://example.net"))
253+ .finish()
254+ }
255+ }
243}
256}