From 480a0993970ba57acdf85e98557d534bb741ea5b Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Sat, 3 Aug 2019 08:50:07 -0400 Subject: [PATCH] Add support for svg by creating elements with correct namespace --- crates/macro/src/html_tree/html_component.rs | 14 +++--- .../macro/src/html_tree/html_dashed_name.rs | 14 ++---- crates/macro/src/html_tree/html_tag/mod.rs | 8 ++-- crates/macro/src/lib.rs | 10 +++++ src/html.rs | 9 ++-- src/virtual_dom/mod.rs | 4 +- src/virtual_dom/vcomp.rs | 4 +- src/virtual_dom/vlist.rs | 6 +-- src/virtual_dom/vnode.rs | 6 +-- src/virtual_dom/vtag.rs | 38 ++++++++++------ src/virtual_dom/vtext.rs | 6 +-- tests/macro/html-tag-pass.rs | 17 ++++++- tests/vtag_test.rs | 44 ++++++++++++++++++- 13 files changed, 125 insertions(+), 55 deletions(-) diff --git a/crates/macro/src/html_tree/html_component.rs b/crates/macro/src/html_tree/html_component.rs index 990c6f7de4b..725b939502d 100644 --- a/crates/macro/src/html_tree/html_component.rs +++ b/crates/macro/src/html_tree/html_component.rs @@ -136,14 +136,12 @@ impl HtmlComponent { } fn peek_type(mut cursor: Cursor) -> Option<()> { - let mut type_str: String = "".to_owned(); let mut colons_optional = true; + let mut last_ident = None; loop { - let mut found_colons = false; let mut post_colons_cursor = cursor; if let Some(c) = Self::double_colon(post_colons_cursor) { - found_colons = true; post_colons_cursor = c; } else if !colons_optional { break; @@ -151,10 +149,7 @@ impl HtmlComponent { if let Some((ident, c)) = post_colons_cursor.ident() { cursor = c; - if found_colons { - type_str += "::"; - } - type_str += &ident.to_string(); + last_ident = Some(ident); } else { break; } @@ -163,8 +158,9 @@ impl HtmlComponent { colons_optional = false; } - (!type_str.is_empty()).as_option()?; - (type_str.to_lowercase() != type_str).as_option() + let type_str = last_ident?.to_string(); + type_str.is_ascii().as_option()?; + type_str.bytes().next()?.is_ascii_uppercase().as_option() } } diff --git a/crates/macro/src/html_tree/html_dashed_name.rs b/crates/macro/src/html_tree/html_dashed_name.rs index e2b6be916f4..f6b894fddf3 100644 --- a/crates/macro/src/html_tree/html_dashed_name.rs +++ b/crates/macro/src/html_tree/html_dashed_name.rs @@ -1,10 +1,11 @@ -use crate::Peek; +use crate::{non_capitalized_ascii, Peek}; use boolinator::Boolinator; use proc_macro2::Ident; use proc_macro2::Span; use quote::{quote, ToTokens}; use std::fmt; use syn::buffer::Cursor; +use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream, Result as ParseResult}; use syn::Token; @@ -36,7 +37,7 @@ impl fmt::Display for HtmlDashedName { impl Peek<'_, Self> for HtmlDashedName { fn peek(cursor: Cursor) -> Option<(Self, Cursor)> { let (name, cursor) = cursor.ident()?; - (name.to_string().to_lowercase() == name.to_string()).as_option()?; + non_capitalized_ascii(&name.to_string()).as_option()?; let mut extended = Vec::new(); let mut cursor = cursor; @@ -58,14 +59,7 @@ impl Peek<'_, Self> for HtmlDashedName { impl Parse for HtmlDashedName { fn parse(input: ParseStream) -> ParseResult { - let name = if let Ok(token) = input.parse::() { - Ident::new("type", token.span).into() - } else if let Ok(token) = input.parse::() { - Ident::new("for", token.span).into() - } else { - input.parse::()?.into() - }; - + let name = input.call(Ident::parse_any)?; let mut extended = Vec::new(); while input.peek(Token![-]) { extended.push((input.parse::()?, input.parse::()?)); diff --git a/crates/macro/src/html_tree/html_tag/mod.rs b/crates/macro/src/html_tree/html_tag/mod.rs index 165eb82ea33..0252f3ed5ef 100644 --- a/crates/macro/src/html_tree/html_tag/mod.rs +++ b/crates/macro/src/html_tree/html_tag/mod.rs @@ -4,7 +4,7 @@ use super::HtmlDashedName as TagName; use super::HtmlProp as TagAttribute; use super::HtmlPropSuffix as TagSuffix; use super::HtmlTree; -use crate::{Peek, PeekValue}; +use crate::{non_capitalized_ascii, Peek, PeekValue}; use boolinator::Boolinator; use proc_macro2::{Delimiter, Span}; use quote::{quote, quote_spanned, ToTokens}; @@ -198,7 +198,7 @@ impl PeekValue for HtmlSelfClosingTag { (punct.as_char() == '<').as_option()?; let (name, mut cursor) = TagName::peek(cursor)?; - (name.to_string().to_lowercase() == name.to_string()).as_option()?; + non_capitalized_ascii(&name.to_string()).as_option()?; let mut after_slash = false; loop { @@ -261,7 +261,7 @@ impl PeekValue for HtmlTagOpen { (punct.as_char() == '<').as_option()?; let (name, _) = TagName::peek(cursor)?; - (name.to_string().to_lowercase() == name.to_string()).as_option()?; + non_capitalized_ascii(&name.to_string()).as_option()?; Some(name) } @@ -320,7 +320,7 @@ impl PeekValue for HtmlTagClose { (punct.as_char() == '/').as_option()?; let (name, cursor) = TagName::peek(cursor)?; - (name.to_string().to_lowercase() == name.to_string()).as_option()?; + non_capitalized_ascii(&name.to_string()).as_option()?; let (punct, _) = cursor.punct()?; (punct.as_char() == '>').as_option()?; diff --git a/crates/macro/src/lib.rs b/crates/macro/src/lib.rs index 48c9a426a68..012f53c5763 100644 --- a/crates/macro/src/lib.rs +++ b/crates/macro/src/lib.rs @@ -75,6 +75,16 @@ trait PeekValue { fn peek(cursor: Cursor) -> Option; } +fn non_capitalized_ascii(string: &str) -> bool { + if !string.is_ascii() { + false + } else if let Some(c) = string.bytes().next() { + c.is_ascii_lowercase() + } else { + false + } +} + #[proc_macro_derive(Properties, attributes(props))] pub fn derive_props(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DerivePropsInput); diff --git a/src/html.rs b/src/html.rs index 31918673de1..246023bb93c 100644 --- a/src/html.rs +++ b/src/html.rs @@ -163,7 +163,7 @@ struct CreatedState { impl> CreatedState { fn update(mut self) -> Self { let mut next_frame = self.component.view(); - let node = next_frame.apply(self.element.as_node(), None, self.last_frame, &self.env); + let node = next_frame.apply(&self.element, None, self.last_frame, &self.env); if let Some(ref mut cell) = self.occupied { *cell.borrow_mut() = node; } @@ -229,7 +229,8 @@ impl Scope where COMP: Component + Renderable, { - pub(crate) fn new() -> Self { + /// visible for testing + pub fn new() -> Self { let shared_state = Rc::new(RefCell::new(ComponentState::Empty)); Scope { shared_state } } @@ -298,12 +299,12 @@ where ComponentState::Created(mut this) => { this.component.destroy(); if let Some(last_frame) = &mut this.last_frame { - last_frame.detach(this.element.as_node()); + last_frame.detach(&this.element); } } ComponentState::Ready(mut this) => { if let Some(ancestor) = &mut this.ancestor { - ancestor.detach(this.element.as_node()); + ancestor.detach(&this.element); } } ComponentState::Empty | ComponentState::Destroyed => {} diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs index 473ac5fae9c..978d2155731 100644 --- a/src/virtual_dom/mod.rs +++ b/src/virtual_dom/mod.rs @@ -76,7 +76,7 @@ pub trait VDiff { type Component: Component; /// Remove itself from parent and return the next sibling. - fn detach(&mut self, parent: &Node) -> Option; + fn detach(&mut self, parent: &Element) -> Option; /// Scoped diff apply to other tree. /// @@ -101,7 +101,7 @@ pub trait VDiff { /// (always removes the `Node` that exists). fn apply( &mut self, - parent: &Node, + parent: &Element, precursor: Option<&Node>, ancestor: Option>, scope: &Scope, diff --git a/src/virtual_dom/vcomp.rs b/src/virtual_dom/vcomp.rs index 4d17749e356..daa99256e32 100644 --- a/src/virtual_dom/vcomp.rs +++ b/src/virtual_dom/vcomp.rs @@ -206,7 +206,7 @@ where type Component = COMP; /// Remove VComp from parent. - fn detach(&mut self, parent: &Node) -> Option { + fn detach(&mut self, parent: &Element) -> Option { match self.state.replace(MountState::Detached) { MountState::Mounted(this) => { (this.destroyer)(); @@ -226,7 +226,7 @@ where /// It compares this with an ancestor `VComp` and overwrites it if it is the same type. fn apply( &mut self, - parent: &Node, + parent: &Element, precursor: Option<&Node>, ancestor: Option>, env: &Scope, diff --git a/src/virtual_dom/vlist.rs b/src/virtual_dom/vlist.rs index 9f603dd34e0..deede464cbe 100644 --- a/src/virtual_dom/vlist.rs +++ b/src/virtual_dom/vlist.rs @@ -1,7 +1,7 @@ //! This module contains fragments implementation. use super::{VDiff, VNode, VText}; use crate::html::{Component, Scope}; -use stdweb::web::Node; +use stdweb::web::{Element, Node}; /// This struct represents a fragment of the Virtual DOM tree. pub struct VList { @@ -24,7 +24,7 @@ impl VList { impl VDiff for VList { type Component = COMP; - fn detach(&mut self, parent: &Node) -> Option { + fn detach(&mut self, parent: &Element) -> Option { let mut last_sibling = None; for mut child in self.childs.drain(..) { last_sibling = child.detach(parent); @@ -34,7 +34,7 @@ impl VDiff for VList { fn apply( &mut self, - parent: &Node, + parent: &Element, precursor: Option<&Node>, ancestor: Option>, env: &Scope, diff --git a/src/virtual_dom/vnode.rs b/src/virtual_dom/vnode.rs index e5aed48a56f..679df81c3b7 100644 --- a/src/virtual_dom/vnode.rs +++ b/src/virtual_dom/vnode.rs @@ -4,7 +4,7 @@ use super::{VComp, VDiff, VList, VTag, VText}; use crate::html::{Component, Renderable, Scope}; use std::cmp::PartialEq; use std::fmt; -use stdweb::web::{INode, Node}; +use stdweb::web::{Element, INode, Node}; /// Bind virtual element to a DOM reference. pub enum VNode { @@ -24,7 +24,7 @@ impl VDiff for VNode { type Component = COMP; /// Remove VNode from parent. - fn detach(&mut self, parent: &Node) -> Option { + fn detach(&mut self, parent: &Element) -> Option { match *self { VNode::VTag(ref mut vtag) => vtag.detach(parent), VNode::VText(ref mut vtext) => vtext.detach(parent), @@ -42,7 +42,7 @@ impl VDiff for VNode { fn apply( &mut self, - parent: &Node, + parent: &Element, precursor: Option<&Node>, ancestor: Option>, env: &Scope, diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs index e350f5eff6e..58190512ddb 100644 --- a/src/virtual_dom/vtag.rs +++ b/src/virtual_dom/vtag.rs @@ -14,6 +14,12 @@ use stdweb::web::{document, Element, EventListenerHandle, IElement, INode, Node} #[allow(unused_imports)] use stdweb::{_js_impl, js}; +/// SVG namespace string used for creating svg elements +pub const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg"; + +/// Default namespace for html elements +pub const HTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml"; + /// A type for a virtual /// [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) /// representation. @@ -344,8 +350,8 @@ impl VTag { } } - // IMPORTANT! This parameters have to be set every time - // to prevent strange behaviour in browser when DOM changed + // IMPORTANT! This parameter has to be set every time + // to prevent strange behaviour in the browser when the DOM changes set_checked(&input, self.checked); } else if let Ok(tae) = TextAreaElement::try_from(element.clone()) { if let Some(change) = self.diff_value(ancestor) { @@ -366,7 +372,7 @@ impl VDiff for VTag { type Component = COMP; /// Remove VTag from parent. - fn detach(&mut self, parent: &Node) -> Option { + fn detach(&mut self, parent: &Element) -> Option { let node = self .reference .take() @@ -382,7 +388,7 @@ impl VDiff for VTag { /// to compute what to patch in the actual DOM nodes. fn apply( &mut self, - parent: &Node, + parent: &Element, precursor: Option<&Node>, ancestor: Option>, env: &Scope, @@ -421,9 +427,18 @@ impl VDiff for VTag { match reform { Reform::Keep => {} Reform::Before(before) => { - let element = document() - .create_element(&self.tag) - .expect("can't create element for vtag"); + let element = if self.tag == "svg" + || parent.namespace_uri() == Some(SVG_NAMESPACE.to_string()) + { + document() + .create_element_ns(SVG_NAMESPACE, &self.tag) + .expect("can't create namespaced element for vtag") + } else { + document() + .create_element(&self.tag) + .expect("can't create element for vtag") + }; + if let Some(sibling) = before { parent .insert_before(&element, &sibling) @@ -472,14 +487,11 @@ impl VDiff for VTag { let mut ancestor_childs = ancestor_childs.drain(..); loop { match (self_childs.next(), ancestor_childs.next()) { - (Some(left), Some(right)) => { - precursor = left.apply(element.as_node(), precursor.as_ref(), Some(right), &env); - } - (Some(left), None) => { - precursor = left.apply(element.as_node(), precursor.as_ref(), None, &env); + (Some(left), right) => { + precursor = left.apply(&element, precursor.as_ref(), right, &env); } (None, Some(ref mut right)) => { - right.detach(element.as_node()); + right.detach(&element); } (None, None) => break, } diff --git a/src/virtual_dom/vtext.rs b/src/virtual_dom/vtext.rs index 3d6f0f55bde..9e3adfd679a 100644 --- a/src/virtual_dom/vtext.rs +++ b/src/virtual_dom/vtext.rs @@ -6,7 +6,7 @@ use log::warn; use std::cmp::PartialEq; use std::fmt; use std::marker::PhantomData; -use stdweb::web::{document, INode, Node, TextNode}; +use stdweb::web::{document, Element, INode, Node, TextNode}; /// A type for a virtual /// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode) @@ -34,7 +34,7 @@ impl VDiff for VText { type Component = COMP; /// Remove VTag from parent. - fn detach(&mut self, parent: &Node) -> Option { + fn detach(&mut self, parent: &Element) -> Option { let node = self .reference .take() @@ -52,7 +52,7 @@ impl VDiff for VText { /// has children and renders them. fn apply( &mut self, - parent: &Node, + parent: &Element, _: Option<&Node>, opposite: Option>, _: &Scope, diff --git a/tests/macro/html-tag-pass.rs b/tests/macro/html-tag-pass.rs index 4170257401a..c933e3add6d 100644 --- a/tests/macro/html-tag-pass.rs +++ b/tests/macro/html-tag-pass.rs @@ -1,4 +1,4 @@ -#![recursion_limit = "256"] +#![recursion_limit = "512"] #[macro_use] mod helpers; @@ -18,6 +18,21 @@ pass_helper! { + + + + + + + + + + + + + + +