on master help sign in
Merge pull request #41 from rylev/svg-support

Start basic SVG support

commit0c2d5f0a9aa7cba4db5fc168399abd4336664561 authorNick Fitzgerald <fitzgen@gmail.com> dateWed 20 Mar 2019, 16:17 (8 years ago) parent5e9415f848215b
8 files changed+187−8
.gitignore+1 js/change-list.js+21 src/builder.rs+89 src/change_list.rs+32 src/node.rs+9−1 src/vdom.rs+19−7 tests/web/main.rs+3 tests/web/render.rs+13
.gitignore+1
@@ -1,3 +1,4 @@
11/target
22**/*.rs.bk
33wasm-pack.log
4+/.vscode
js/change-list.js+21
@@ -175,6 +175,27 @@ const OP_TABLE = [
175175 const id = mem32[i++];
176176 changeList.dropString(id);
177177 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;
178199 }
179200];
180201
src/builder.rs+89
@@ -20,6 +20,7 @@ where
2020 listeners: Listeners,
2121 attributes: Attributes,
2222 children: Children,
23+ namespace: Option<&'a str>,
2324}
2425
2526impl<'a>
@@ -60,6 +61,7 @@ impl<'a>
6061 listeners: bumpalo::collections::Vec::new_in(bump),
6162 attributes: bumpalo::collections::Vec::new_in(bump),
6263 children: bumpalo::collections::Vec::new_in(bump),
64+ namespace: None,
6365 }
6466 }
6567}
@@ -108,6 +110,7 @@ where
108110 listeners,
109111 attributes: self.attributes,
110112 children: self.children,
113+ namespace: self.namespace,
111114 }
112115 }
113116
@@ -145,6 +148,7 @@ where
145148 listeners: self.listeners,
146149 attributes,
147150 children: self.children,
151+ namespace: self.namespace,
148152 }
149153 }
150154
@@ -182,6 +186,33 @@ where
182186 listeners: self.listeners,
183187 attributes: self.attributes,
184188 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,
185216 }
186217 }
187218
@@ -208,6 +239,7 @@ where
208239 self.listeners,
209240 self.attributes,
210241 self.children,
242+ self.namespace,
211243 )
212244 }
213245}
@@ -357,6 +389,26 @@ macro_rules! builder_constructors {
357389 ElementBuilder::new(bump, stringify!($name))
358390 }
359391 )*
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+ )*
360412 }
361413}
362414
@@ -861,6 +913,43 @@ builder_constructors! {
861913 template;
862914}
863915
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+
864953/// Construct a text node.
865954///
866955/// This is `dodrio`'s virtual DOM equivalent of `document.createTextNode`.
src/change_list.rs+32
@@ -267,6 +267,24 @@ enum ChangeDiscriminant {
267267 /// dropString(id);
268268 /// ```
269269 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,
270288}
271289
272290// Allocation utilities to ensure that we only allocation sequences of `u32`s
@@ -361,6 +379,13 @@ impl ChangeList {
361379 self.op2(ChangeDiscriminant::SetAttribute, name_id, value_id);
362380 }
363381
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+
364389 pub(crate) fn emit_remove_attribute(&mut self, name: &str) {
365390 debug!("emit_remove_attribute({:?})", name);
366391 let name_id = self.ensure_string(name);
@@ -402,6 +427,13 @@ impl ChangeList {
402427 self.op1(ChangeDiscriminant::CreateElement, tag_name_id);
403428 }
404429
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+
405437 pub(crate) fn emit_new_event_listener(&mut self, listener: &Listener) {
406438 debug!("emit_new_event_listener({:?})", listener);
407439 let (a, b) = listener.get_callback_parts();
src/node.rs+9−1
@@ -29,6 +29,7 @@ pub struct ElementNode<'a> {
2929 pub(crate) listeners: &'a [Listener<'a>],
3030 pub(crate) attributes: &'a [Attribute<'a>],
3131 pub(crate) children: &'a [Node<'a>],
32+ pub(crate) namespace: Option<&'a str>,
3233}
3334
3435/// An event listener callback function.
@@ -96,7 +97,7 @@ impl<'a> Attribute<'a> {
9697}
9798
9899impl<'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
100101 #[inline]
101102 pub(crate) fn element<Listeners, Attributes, Children>(
102103 bump: &'a Bump,
@@ -104,6 +105,7 @@ impl<'a> Node<'a> {
104105 listeners: Listeners,
105106 attributes: Attributes,
106107 children: Children,
108+ namespace: Option<&'a str>,
107109 ) -> Node<'a>
108110 where
109111 Listeners: 'a + AsRef<[Listener<'a>]>,
@@ -124,6 +126,7 @@ impl<'a> Node<'a> {
124126 listeners,
125127 attributes,
126128 children,
129+ namespace,
127130 })
128131 }
129132
@@ -188,6 +191,11 @@ impl<'a> ElementNode<'a> {
188191 pub fn children(&self) -> &'a [Node<'a>] {
189192 self.children
190193 }
194+
195+ /// Get this element's namespace.
196+ pub fn namespace(&self) -> Option<&'a str> {
197+ self.namespace
198+ }
191199}
192200
193201union CallbackFatPtr<'a> {
src/vdom.rs+19−7
@@ -138,7 +138,7 @@ impl Vdom {
138138 container.set_inner_html("");
139139
140140 // 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);
142142 let current_root = Some(unsafe { extend_node_lifetime(current_root) });
143143 let window = web_sys::window().expect("should have access to the Window");
144144 let document = window
@@ -287,12 +287,12 @@ impl VdomInnerExclusive {
287287 self.change_list.emit_set_text(new_text);
288288 }
289289 }
290− (&Node::Text(TextNode { .. }), Node::Element(ElementNode { .. })) => {
290+ (&Node::Text(_), Node::Element(_)) => {
291291 debug!(" replacing a text node with an element");
292292 self.create(registry, new);
293293 self.change_list.emit_replace_with();
294294 }
295− (&Node::Element(ElementNode { .. }), Node::Text(TextNode { .. })) => {
295+ (&Node::Element(_), Node::Text(_)) => {
296296 debug!(" replacing an element with a text node");
297297 self.create(registry, new);
298298 self.change_list.emit_replace_with();
@@ -303,17 +303,19 @@ impl VdomInnerExclusive {
303303 listeners: new_listeners,
304304 attributes: new_attributes,
305305 children: new_children,
306+ namespace: new_namespace,
306307 }),
307308 Node::Element(ElementNode {
308309 tag_name: old_tag_name,
309310 listeners: old_listeners,
310311 attributes: old_attributes,
311312 children: old_children,
313+ namespace: old_namespace,
312314 }),
313315 ) => {
314316 debug!(" updating an element");
315− if new_tag_name != old_tag_name {
316− debug!(" different tag names; creating new element and replacing old element");
317+ if new_tag_name != old_tag_name || new_namespace != old_namespace {
318+ debug!(" different tag names or namespaces; creating new element and replacing old element");
317319 self.create(registry, new);
318320 self.change_list.emit_replace_with();
319321 return;
@@ -462,8 +464,13 @@ impl VdomInnerExclusive {
462464 listeners,
463465 attributes,
464466 children,
467+ namespace,
465468 }) => {
466− self.change_list.emit_create_element(tag_name);
469+ if let Some(namespace) = namespace {
470+ self.change_list.emit_create_element_ns(tag_name, namespace);
471+ } else {
472+ self.change_list.emit_create_element(tag_name);
473+ }
467474 for l in listeners {
468475 unsafe {
469476 registry.add(l);
@@ -471,7 +478,12 @@ impl VdomInnerExclusive {
471478 self.change_list.emit_new_event_listener(l);
472479 }
473480 for attr in attributes {
474− self.change_list.emit_set_attribute(&attr.name, &attr.value);
481+ if namespace.is_none() || attr.name.starts_with("xmlns") {
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+ }
475487 }
476488 for child in children {
477489 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) {
9292 .expect("`actual` should be an `Element`");
9393 check_attributes(actual.attributes(), elem.attributes());
9494 check_children(actual.child_nodes(), elem.children());
95+ if let Some(namespace) = elem.namespace() {
96+ assert_eq!(actual.namespace_uri(), Some(namespace.into()))
97+ }
9598 }
9699 }
97100 }
tests/web/render.rs+13
@@ -240,4 +240,17 @@ before_after! {
240240 .finish()
241241 }
242242 }
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+ }
243256}