diff --git a/examples/futures/src/markdown.rs b/examples/futures/src/markdown.rs index 5c85b7a55e4..afdad6bf98f 100644 --- a/examples/futures/src/markdown.rs +++ b/examples/futures/src/markdown.rs @@ -1,5 +1,7 @@ /// Original author of this code is [Nathan Ringo](https://github.com/remexre) /// Source: https://github.com/acmumn/mentoring/blob/master/web-client/src/view/markdown.rs +use std::rc::Rc; + use pulldown_cmark::{Alignment, CodeBlockKind, Event, Options, Parser, Tag}; use yew::virtual_dom::{VNode, VTag, VText}; use yew::{html, Classes, Html}; @@ -56,16 +58,22 @@ pub fn render_markdown(src: &str) -> Html { if let Some(top_children) = top.children_mut() { for r in top_children.to_vlist_mut().iter_mut() { if let VNode::VTag(ref mut vtag) = r { - if let Some(vtag_children) = vtag.children_mut() { + if let Some(vtag_children) = Rc::make_mut(vtag).children_mut() { for (i, c) in vtag_children.to_vlist_mut().iter_mut().enumerate() { if let VNode::VTag(ref mut vtag) = c { match aligns[i] { Alignment::None => {} - Alignment::Left => add_class(vtag, "text-left"), - Alignment::Center => add_class(vtag, "text-center"), - Alignment::Right => add_class(vtag, "text-right"), + Alignment::Left => { + add_class(Rc::make_mut(vtag), "text-left") + } + Alignment::Center => { + add_class(Rc::make_mut(vtag), "text-center") + } + Alignment::Right => { + add_class(Rc::make_mut(vtag), "text-right") + } } } } @@ -79,7 +87,7 @@ pub fn render_markdown(src: &str) -> Html { if let VNode::VTag(ref mut vtag) = c { // TODO // vtag.tag = "th".into(); - vtag.add_attribute("scope", "col"); + Rc::make_mut(vtag).add_attribute("scope", "col"); } } } @@ -99,7 +107,7 @@ pub fn render_markdown(src: &str) -> Html { } if elems.len() == 1 { - VNode::VTag(Box::new(elems.pop().unwrap())) + VNode::VTag(Rc::new(elems.pop().unwrap())) } else { html! {
{ for elems.into_iter() }
diff --git a/packages/yew-macro/src/html_tree/html_element.rs b/packages/yew-macro/src/html_tree/html_element.rs index 0124dd0c0d7..a501644941b 100644 --- a/packages/yew-macro/src/html_tree/html_element.rs +++ b/packages/yew-macro/src/html_tree/html_element.rs @@ -359,7 +359,7 @@ impl ToTokens for HtmlElement { quote! { ::std::convert::Into::<::yew::virtual_dom::VNode>::into( ::yew::virtual_dom::VTag::__new_other( - ::std::borrow::Cow::<'static, ::std::primitive::str>::Borrowed(#name), + ::yew::virtual_dom::AttrValue::Static(#name), #node_ref, #key, #attributes, @@ -416,7 +416,7 @@ impl ToTokens for HtmlElement { // (note the extra braces). Hence the need for the `allow`. // Anyways to remove the braces? let mut #vtag_name = ::std::convert::Into::< - ::std::borrow::Cow::<'static, ::std::primitive::str> + ::yew::virtual_dom::AttrValue >::into(#expr); ::std::debug_assert!( #vtag_name.is_ascii(), diff --git a/packages/yew-macro/src/html_tree/html_list.rs b/packages/yew-macro/src/html_tree/html_list.rs index e48f250bfde..301b533a92c 100644 --- a/packages/yew-macro/src/html_tree/html_list.rs +++ b/packages/yew-macro/src/html_tree/html_list.rs @@ -78,9 +78,9 @@ impl ToTokens for HtmlList { }; tokens.extend(quote_spanned! {spanned.span()=> - ::yew::virtual_dom::VNode::VList( + ::yew::virtual_dom::VNode::VList(::std::rc::Rc::new( ::yew::virtual_dom::VList::with_children(#children, #key) - ) + )) }); } } diff --git a/packages/yew-macro/src/html_tree/mod.rs b/packages/yew-macro/src/html_tree/mod.rs index 0361231b6d4..266ea31bfd8 100644 --- a/packages/yew-macro/src/html_tree/mod.rs +++ b/packages/yew-macro/src/html_tree/mod.rs @@ -123,7 +123,7 @@ impl ToTokens for HtmlTree { lint::lint_all(self); match self { HtmlTree::Empty => tokens.extend(quote! { - ::yew::virtual_dom::VNode::VList(::yew::virtual_dom::VList::new()) + <::yew::virtual_dom::VNode as ::std::default::Default>::default() }), HtmlTree::Component(comp) => comp.to_tokens(tokens), HtmlTree::Element(tag) => tag.to_tokens(tokens), @@ -375,9 +375,9 @@ impl ToTokens for HtmlRootBraced { tokens.extend(quote_spanned! {brace.span.span()=> { - ::yew::virtual_dom::VNode::VList( + ::yew::virtual_dom::VNode::VList(::std::rc::Rc::new( ::yew::virtual_dom::VList::with_children(#children, ::std::option::Option::None) - ) + )) } }); } diff --git a/packages/yew-macro/tests/html_macro/component-any-children-pass.rs b/packages/yew-macro/tests/html_macro/component-any-children-pass.rs index cf5a924e506..c9b9e0f1777 100644 --- a/packages/yew-macro/tests/html_macro/component-any-children-pass.rs +++ b/packages/yew-macro/tests/html_macro/component-any-children-pass.rs @@ -83,12 +83,12 @@ impl ::std::convert::From<::yew::virtual_dom::VChild> for ChildrenVari impl ::std::convert::Into<::yew::virtual_dom::VNode> for ChildrenVariants { fn into(self) -> ::yew::virtual_dom::VNode { match self { - Self::Child(comp) => ::yew::virtual_dom::VNode::VComp(::std::convert::Into::< + Self::Child(comp) => ::yew::virtual_dom::VNode::VComp(::std::rc::Rc::new(::std::convert::Into::< ::yew::virtual_dom::VComp, - >::into(comp)), - Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::convert::Into::< + >::into(comp))), + Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::rc::Rc::new(::std::convert::Into::< ::yew::virtual_dom::VComp, - >::into(comp)), + >::into(comp))), } } } diff --git a/packages/yew-macro/tests/html_macro/component-pass.rs b/packages/yew-macro/tests/html_macro/component-pass.rs index 61f0de060f8..f971f66c4cb 100644 --- a/packages/yew-macro/tests/html_macro/component-pass.rs +++ b/packages/yew-macro/tests/html_macro/component-pass.rs @@ -82,12 +82,12 @@ impl ::std::convert::From<::yew::virtual_dom::VChild> for ChildrenVari impl ::std::convert::Into<::yew::virtual_dom::VNode> for ChildrenVariants { fn into(self) -> ::yew::virtual_dom::VNode { match self { - Self::Child(comp) => ::yew::virtual_dom::VNode::VComp(::std::convert::Into::< + Self::Child(comp) => ::yew::virtual_dom::VNode::VComp(::std::rc::Rc::new(::std::convert::Into::< ::yew::virtual_dom::VComp, - >::into(comp)), - Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::convert::Into::< + >::into(comp))), + Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::rc::Rc::new(::std::convert::Into::< ::yew::virtual_dom::VComp, - >::into(comp)), + >::into(comp))), } } } diff --git a/packages/yew-macro/tests/html_macro/element-fail.stderr b/packages/yew-macro/tests/html_macro/element-fail.stderr index d05a7cfad41..842553fab43 100644 --- a/packages/yew-macro/tests/html_macro/element-fail.stderr +++ b/packages/yew-macro/tests/html_macro/element-fail.stderr @@ -703,20 +703,15 @@ error[E0277]: the trait bound `(): IntoPropValue` is not satisfied and $N others = note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the trait bound `Cow<'static, str>: From<{integer}>` is not satisfied +error[E0277]: the trait bound `implicit_clone::unsync::IString: From<{integer}>` is not satisfied --> tests/html_macro/element-fail.rs:77:15 | 77 | html! { <@{55}> }; - | ^^^^ the trait `From<{integer}>` is not implemented for `Cow<'static, str>` + | ^^^^ the trait `From<{integer}>` is not implemented for `implicit_clone::unsync::IString` | = help: the following other types implement trait `From`: - as From<&'a CStr>> - as From<&'a CString>> - as From> - as From<&'a OsStr>> - as From<&'a OsString>> - as From> - as From<&'a Path>> - as From<&'a PathBuf>> - and $N others - = note: required because of the requirements on the impl of `Into>` for `{integer}` + > + >> + >> + > + = note: required because of the requirements on the impl of `Into` for `{integer}` diff --git a/packages/yew/src/dom_bundle/blist.rs b/packages/yew/src/dom_bundle/blist.rs index 93a9af585a8..ddd909027ea 100644 --- a/packages/yew/src/dom_bundle/blist.rs +++ b/packages/yew/src/dom_bundle/blist.rs @@ -4,13 +4,13 @@ use std::cmp::Ordering; use std::collections::HashSet; use std::hash::Hash; use std::ops::Deref; -use std::rc::Rc; use web_sys::Element; use super::{test_log, BNode, BSubtree, DomSlot}; use crate::dom_bundle::{Reconcilable, ReconcileTarget}; use crate::html::AnyScope; +use crate::utils::RcExt; use crate::virtual_dom::{Key, VList, VNode, VText}; /// This struct represents a mounted [VList] @@ -30,10 +30,8 @@ impl VList { let children = self .children - .map(Rc::try_unwrap) - .unwrap_or_else(|| Ok(Vec::new())) - // Rc::unwrap_or_clone is not stable yet. - .unwrap_or_else(|m| m.to_vec()); + .map(RcExt::unwrap_or_clone) + .unwrap_or_default(); (self.key, fully_keyed, children) } diff --git a/packages/yew/src/dom_bundle/bnode.rs b/packages/yew/src/dom_bundle/bnode.rs index d6fdbe5aa47..2f59acfe47f 100644 --- a/packages/yew/src/dom_bundle/bnode.rs +++ b/packages/yew/src/dom_bundle/bnode.rs @@ -7,6 +7,7 @@ use web_sys::{Element, Node}; use super::{BComp, BList, BPortal, BRaw, BSubtree, BSuspense, BTag, BText, DomSlot}; use crate::dom_bundle::{Reconcilable, ReconcileTarget}; use crate::html::AnyScope; +use crate::utils::RcExt; use crate::virtual_dom::{Key, VNode}; /// The bundle implementation to [VNode]. @@ -95,7 +96,8 @@ impl Reconcilable for VNode { ) -> (DomSlot, Self::Bundle) { match self { VNode::VTag(vtag) => { - let (node_ref, tag) = vtag.attach(root, parent_scope, parent, slot); + let (node_ref, tag) = + RcExt::unwrap_or_clone(vtag).attach(root, parent_scope, parent, slot); (node_ref, tag.into()) } VNode::VText(vtext) => { @@ -103,11 +105,13 @@ impl Reconcilable for VNode { (node_ref, text.into()) } VNode::VComp(vcomp) => { - let (node_ref, comp) = vcomp.attach(root, parent_scope, parent, slot); + let (node_ref, comp) = + RcExt::unwrap_or_clone(vcomp).attach(root, parent_scope, parent, slot); (node_ref, comp.into()) } VNode::VList(vlist) => { - let (node_ref, list) = vlist.attach(root, parent_scope, parent, slot); + let (node_ref, list) = + RcExt::unwrap_or_clone(vlist).attach(root, parent_scope, parent, slot); (node_ref, list.into()) } VNode::VRef(node) => { @@ -115,11 +119,13 @@ impl Reconcilable for VNode { (DomSlot::at(node.clone()), BNode::Ref(node)) } VNode::VPortal(vportal) => { - let (node_ref, portal) = vportal.attach(root, parent_scope, parent, slot); + let (node_ref, portal) = + RcExt::unwrap_or_clone(vportal).attach(root, parent_scope, parent, slot); (node_ref, portal.into()) } VNode::VSuspense(vsuspsense) => { - let (node_ref, suspsense) = vsuspsense.attach(root, parent_scope, parent, slot); + let (node_ref, suspsense) = + RcExt::unwrap_or_clone(vsuspsense).attach(root, parent_scope, parent, slot); (node_ref, suspsense.into()) } VNode::VRaw(vraw) => { @@ -149,20 +155,46 @@ impl Reconcilable for VNode { bundle: &mut BNode, ) -> DomSlot { match self { - VNode::VTag(vtag) => vtag.reconcile_node(root, parent_scope, parent, slot, bundle), + VNode::VTag(vtag) => RcExt::unwrap_or_clone(vtag).reconcile_node( + root, + parent_scope, + parent, + slot, + bundle, + ), VNode::VText(vtext) => vtext.reconcile_node(root, parent_scope, parent, slot, bundle), - VNode::VComp(vcomp) => vcomp.reconcile_node(root, parent_scope, parent, slot, bundle), - VNode::VList(vlist) => vlist.reconcile_node(root, parent_scope, parent, slot, bundle), + VNode::VComp(vcomp) => RcExt::unwrap_or_clone(vcomp).reconcile_node( + root, + parent_scope, + parent, + slot, + bundle, + ), + VNode::VList(vlist) => RcExt::unwrap_or_clone(vlist).reconcile_node( + root, + parent_scope, + parent, + slot, + bundle, + ), VNode::VRef(node) => match bundle { BNode::Ref(ref n) if &node == n => DomSlot::at(node), _ => VNode::VRef(node).replace(root, parent_scope, parent, slot, bundle), }, - VNode::VPortal(vportal) => { - vportal.reconcile_node(root, parent_scope, parent, slot, bundle) - } - VNode::VSuspense(vsuspsense) => { - vsuspsense.reconcile_node(root, parent_scope, parent, slot, bundle) - } + VNode::VPortal(vportal) => RcExt::unwrap_or_clone(vportal).reconcile_node( + root, + parent_scope, + parent, + slot, + bundle, + ), + VNode::VSuspense(vsuspsense) => RcExt::unwrap_or_clone(vsuspsense).reconcile_node( + root, + parent_scope, + parent, + slot, + bundle, + ), VNode::VRaw(vraw) => vraw.reconcile_node(root, parent_scope, parent, slot, bundle), } } @@ -246,10 +278,16 @@ mod feat_hydration { fragment: &mut Fragment, ) -> Self::Bundle { match self { - VNode::VTag(vtag) => vtag.hydrate(root, parent_scope, parent, fragment).into(), + VNode::VTag(vtag) => RcExt::unwrap_or_clone(vtag) + .hydrate(root, parent_scope, parent, fragment) + .into(), VNode::VText(vtext) => vtext.hydrate(root, parent_scope, parent, fragment).into(), - VNode::VComp(vcomp) => vcomp.hydrate(root, parent_scope, parent, fragment).into(), - VNode::VList(vlist) => vlist.hydrate(root, parent_scope, parent, fragment).into(), + VNode::VComp(vcomp) => RcExt::unwrap_or_clone(vcomp) + .hydrate(root, parent_scope, parent, fragment) + .into(), + VNode::VList(vlist) => RcExt::unwrap_or_clone(vlist) + .hydrate(root, parent_scope, parent, fragment) + .into(), // You cannot hydrate a VRef. VNode::VRef(_) => { panic!( @@ -264,7 +302,7 @@ mod feat_hydration { use_effect." ) } - VNode::VSuspense(vsuspense) => vsuspense + VNode::VSuspense(vsuspense) => RcExt::unwrap_or_clone(vsuspense) .hydrate(root, parent_scope, parent, fragment) .into(), VNode::VRaw(vraw) => vraw.hydrate(root, parent_scope, parent, fragment).into(), diff --git a/packages/yew/src/dom_bundle/bportal.rs b/packages/yew/src/dom_bundle/bportal.rs index a7e5d76c9a0..9792383101b 100644 --- a/packages/yew/src/dom_bundle/bportal.rs +++ b/packages/yew/src/dom_bundle/bportal.rs @@ -123,6 +123,8 @@ impl BPortal { mod layout_tests { extern crate self as yew; + use std::rc::Rc; + use gloo::utils::document; use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; use web_sys::HtmlInputElement; @@ -151,10 +153,10 @@ mod layout_tests {
{VNode::VRef(first_target.clone().into())} {VNode::VRef(second_target.clone().into())} - {VNode::VPortal(VPortal::new( + {VNode::VPortal(Rc::new(VPortal::new( html! { {"PORTAL"} }, first_target.clone(), - ))} + )))} {"AFTER"}
}, @@ -166,10 +168,10 @@ mod layout_tests {
{VNode::VRef(first_target.clone().into())} {VNode::VRef(second_target.clone().into())} - {VNode::VPortal(VPortal::new( + {VNode::VPortal(Rc::new(VPortal::new( html! { {"PORTAL"} }, second_target.clone(), - ))} + )))} {"AFTER"}
}, @@ -181,10 +183,10 @@ mod layout_tests {
{VNode::VRef(first_target.clone().into())} {VNode::VRef(second_target.clone().into())} - {VNode::VPortal(VPortal::new( + {VNode::VPortal(Rc::new(VPortal::new( html! { <> {"PORTAL"} }, second_target.clone(), - ))} + )))} {"AFTER"}
}, @@ -207,11 +209,11 @@ mod layout_tests { node: html! {
{VNode::VRef(target_with_child.clone().into())} - {VNode::VPortal(VPortal::new_before( + {VNode::VPortal(Rc::new(VPortal::new_before( html! { {"PORTAL"} }, target_with_child.clone(), Some(target_child.clone().into()), - ))} + )))}
}, expected: "
PORTAL
", diff --git a/packages/yew/src/dom_bundle/btag/mod.rs b/packages/yew/src/dom_bundle/btag/mod.rs index c590d6be6c5..8a97c1ef33d 100644 --- a/packages/yew/src/dom_bundle/btag/mod.rs +++ b/packages/yew/src/dom_bundle/btag/mod.rs @@ -3,7 +3,6 @@ mod attributes; mod listeners; -use std::borrow::Cow; use std::cell::RefCell; use std::collections::HashMap; use std::hint::unreachable_unchecked; @@ -18,7 +17,7 @@ use web_sys::{Element, HtmlTextAreaElement as TextAreaElement}; use super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget}; use crate::html::AnyScope; use crate::virtual_dom::vtag::{InputFields, VTagInner, Value, MATHML_NAMESPACE, SVG_NAMESPACE}; -use crate::virtual_dom::{Attributes, Key, VTag}; +use crate::virtual_dom::{AttrValue, Attributes, Key, VTag}; use crate::NodeRef; /// Applies contained changes to DOM [web_sys::Element] @@ -51,7 +50,7 @@ enum BTagInner { /// Fields for all other kinds of [VTag]s Other { /// A tag of the element. - tag: Cow<'static, str>, + tag: AttrValue, /// Child node. child_bundle: BNode, }, @@ -407,6 +406,8 @@ mod feat_hydration { #[cfg(target_arch = "wasm32")] #[cfg(test)] mod tests { + use std::rc::Rc; + use wasm_bindgen::JsCast; use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; use web_sys::HtmlInputElement as InputElement; @@ -414,6 +415,7 @@ mod tests { use super::*; use crate::dom_bundle::utils::setup_parent; use crate::dom_bundle::{BNode, Reconcilable, ReconcileTarget}; + use crate::utils::RcExt; use crate::virtual_dom::vtag::{HTML_NAMESPACE, SVG_NAMESPACE}; use crate::virtual_dom::{AttrValue, VNode, VTag}; use crate::{html, Html, NodeRef}; @@ -564,7 +566,7 @@ mod tests { fn assert_vtag(node: VNode) -> VTag { if let VNode::VTag(vtag) = node { - return *vtag; + return RcExt::unwrap_or_clone(vtag); } panic!("should be vtag"); } @@ -971,7 +973,7 @@ mod tests { vtag.add_attribute("disabled", "disabled"); vtag.add_attribute("tabindex", "0"); - let elem = VNode::VTag(Box::new(vtag)); + let elem = VNode::VTag(Rc::new(vtag)); let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end()); @@ -979,7 +981,7 @@ mod tests { let mut vtag = VTag::new("div"); vtag.node_ref = test_ref.clone(); vtag.add_attribute("tabindex", "0"); - let next_elem = VNode::VTag(Box::new(vtag)); + let next_elem = VNode::VTag(Rc::new(vtag)); let elem_vtag = assert_vtag(next_elem); // Sync happens here diff --git a/packages/yew/src/html/classes.rs b/packages/yew/src/html/classes.rs index 0a5bb9cc9b0..941ec617bf2 100644 --- a/packages/yew/src/html/classes.rs +++ b/packages/yew/src/html/classes.rs @@ -6,6 +6,7 @@ use implicit_clone::ImplicitClone; use indexmap::IndexSet; use super::IntoPropValue; +use crate::utils::RcExt; use crate::virtual_dom::AttrValue; /// A set of classes, cheap to clone. @@ -150,10 +151,7 @@ impl IntoIterator for Classes { #[inline] fn into_iter(self) -> Self::IntoIter { - // NOTE: replace this by Rc::unwrap_or_clone() when it becomes stable - Rc::try_unwrap(self.set) - .unwrap_or_else(|rc| (*rc).clone()) - .into_iter() + RcExt::unwrap_or_clone(self.set).into_iter() } } diff --git a/packages/yew/src/html/component/children.rs b/packages/yew/src/html/component/children.rs index 5118c99f84b..af004d96ff9 100644 --- a/packages/yew/src/html/component/children.rs +++ b/packages/yew/src/html/component/children.rs @@ -1,6 +1,7 @@ //! Component children module use std::fmt; +use std::rc::Rc; use crate::html::Html; use crate::virtual_dom::{VChild, VComp, VList, VNode}; @@ -241,7 +242,7 @@ impl From> for Html { } } - Html::VList(val.into()) + Html::VList(Rc::new(val.into())) } } diff --git a/packages/yew/src/html/conversion/to_html.rs b/packages/yew/src/html/conversion/to_html.rs index c7d3eeaa57b..af15d5a179e 100644 --- a/packages/yew/src/html/conversion/to_html.rs +++ b/packages/yew/src/html/conversion/to_html.rs @@ -46,18 +46,18 @@ where { #[inline(always)] fn to_html(&self) -> Html { - Html::VList(VList::with_children( + Html::VList(Rc::new(VList::with_children( self.iter().map(ToHtml::to_html).collect(), None, - )) + ))) } #[inline(always)] fn into_html(self) -> Html { - Html::VList(VList::with_children( + Html::VList(Rc::new(VList::with_children( self.into_iter().map(ToHtml::into_html).collect(), None, - )) + ))) } } @@ -81,7 +81,7 @@ impl ToHtml for Vec { #[inline(always)] fn into_html(self) -> Html { - Html::VList(VList::with_children(self, None)) + Html::VList(Rc::new(VList::with_children(self, None))) } } @@ -105,7 +105,7 @@ impl ToHtml for VList { #[inline(always)] fn into_html(self) -> Html { - Html::VList(self) + Html::VList(Rc::new(self)) } } @@ -132,7 +132,7 @@ where #[inline(always)] fn into_html(self) -> Html { - VNode::VComp(self.into()) + VNode::VComp(Rc::new(self.into())) } } diff --git a/packages/yew/src/html/mod.rs b/packages/yew/src/html/mod.rs index 87e3cc03372..cbb0b1c5488 100644 --- a/packages/yew/src/html/mod.rs +++ b/packages/yew/src/html/mod.rs @@ -142,5 +142,5 @@ mod feat_csr { /// ## Relevant examples /// - [Portals](https://github.com/yewstack/yew/tree/master/examples/portals) pub fn create_portal(child: Html, host: Element) -> Html { - VNode::VPortal(VPortal::new(child, host)) + VNode::VPortal(Rc::new(VPortal::new(child, host))) } diff --git a/packages/yew/src/utils/mod.rs b/packages/yew/src/utils/mod.rs index 98934ed054b..4f33ea672b2 100644 --- a/packages/yew/src/utils/mod.rs +++ b/packages/yew/src/utils/mod.rs @@ -64,3 +64,14 @@ pub fn print_node(n: &web_sys::Node) -> String { None => n.text_content().unwrap_or_default(), } } + +// NOTE: replace this by Rc::unwrap_or_clone() when it becomes stable +pub(crate) trait RcExt { + fn unwrap_or_clone(this: Self) -> T; +} + +impl RcExt for std::rc::Rc { + fn unwrap_or_clone(this: Self) -> T { + std::rc::Rc::try_unwrap(this).unwrap_or_else(|rc| (*rc).clone()) + } +} diff --git a/packages/yew/src/virtual_dom/listeners.rs b/packages/yew/src/virtual_dom/listeners.rs index caa0943d599..f96dead2f39 100644 --- a/packages/yew/src/virtual_dom/listeners.rs +++ b/packages/yew/src/virtual_dom/listeners.rs @@ -1,5 +1,7 @@ use std::rc::Rc; +use crate::html::ImplicitClone; + /// The [Listener] trait is an universal implementation of an event listener /// which is used to bind Rust-listener to JS-listener (DOM). pub trait Listener { @@ -168,6 +170,8 @@ pub enum Listeners { Pending(Box<[Option>]>), } +impl ImplicitClone for Listeners {} + impl PartialEq for Listeners { fn eq(&self, rhs: &Self) -> bool { use Listeners::*; diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 940c46021f9..6ede1e6e074 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -22,6 +22,7 @@ pub mod vtag; pub mod vtext; use std::hint::unreachable_unchecked; +use std::rc::Rc; use indexmap::IndexMap; @@ -204,7 +205,7 @@ pub enum Attributes { /// IndexMap is used to provide runtime attribute deduplication in cases where the html! macro /// was not used to guarantee it. - IndexMap(IndexMap), + IndexMap(Rc>), } impl Attributes { @@ -233,7 +234,7 @@ impl Attributes { macro_rules! unpack { () => { match self { - Self::IndexMap(m) => m, + Self::IndexMap(m) => Rc::make_mut(m), // SAFETY: unreachable because we set self to the `IndexMap` variant above. _ => unsafe { unreachable_unchecked() }, } @@ -241,23 +242,23 @@ impl Attributes { } match self { - Self::IndexMap(m) => m, + Self::IndexMap(m) => Rc::make_mut(m), Self::Static(arr) => { - *self = Self::IndexMap( + *self = Self::IndexMap(Rc::new( arr.iter() .map(|(k, v, ty)| ((*k).into(), ((*v).into(), *ty))) .collect(), - ); + )); unpack!() } Self::Dynamic { keys, values } => { - *self = Self::IndexMap( + *self = Self::IndexMap(Rc::new( std::mem::take(values) .iter_mut() .zip(keys.iter()) .filter_map(|(v, k)| v.take().map(|v| (AttrValue::from(*k), v))) .collect(), - ); + )); unpack!() } } @@ -270,7 +271,7 @@ impl From> for Attributes { .into_iter() .map(|(k, v)| (k, (v, ApplyAttributeAs::Attribute))) .collect(); - Self::IndexMap(v) + Self::IndexMap(Rc::new(v)) } } @@ -280,7 +281,7 @@ impl From> for Attributes { .into_iter() .map(|(k, v)| (AttrValue::Static(k), (v, ApplyAttributeAs::Attribute))) .collect(); - Self::IndexMap(v) + Self::IndexMap(Rc::new(v)) } } diff --git a/packages/yew/src/virtual_dom/vlist.rs b/packages/yew/src/virtual_dom/vlist.rs index cd9459497f1..62dc5b14e0a 100644 --- a/packages/yew/src/virtual_dom/vlist.rs +++ b/packages/yew/src/virtual_dom/vlist.rs @@ -3,6 +3,7 @@ use std::ops::{Deref, DerefMut}; use std::rc::Rc; use super::{Key, VNode}; +use crate::html::ImplicitClone; #[derive(Clone, Copy, Debug, PartialEq)] enum FullyKeyedState { @@ -23,6 +24,8 @@ pub struct VList { pub key: Option, } +impl ImplicitClone for VList {} + impl PartialEq for VList { fn eq(&self, other: &Self) -> bool { self.key == other.key && self.children == other.children diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 943081c2f3e..8ed18f29c9f 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -2,12 +2,13 @@ use std::cmp::PartialEq; use std::iter::FromIterator; +use std::rc::Rc; use std::{fmt, mem}; use web_sys::Node; use super::{Key, VChild, VComp, VList, VPortal, VSuspense, VTag, VText}; -use crate::html::BaseComponent; +use crate::html::{BaseComponent, ImplicitClone}; use crate::virtual_dom::VRaw; use crate::AttrValue; @@ -16,25 +17,27 @@ use crate::AttrValue; #[must_use = "html does not do anything unless returned to Yew for rendering."] pub enum VNode { /// A bind between `VTag` and `Element`. - VTag(Box), + VTag(Rc), /// A bind between `VText` and `TextNode`. VText(VText), /// A bind between `VComp` and `Element`. - VComp(VComp), + VComp(Rc), /// A holder for a list of other nodes. - VList(VList), + VList(Rc), /// A portal to another part of the document - VPortal(VPortal), + VPortal(Rc), /// A holder for any `Node` (necessary for replacing node). VRef(Node), /// A suspendible document fragment. - VSuspense(VSuspense), + VSuspense(Rc), /// A raw HTML string, represented by [`AttrValue`](crate::AttrValue). /// /// Also see: [`VNode::from_html_unchecked`] VRaw(VRaw), } +impl ImplicitClone for VNode {} + impl VNode { pub fn key(&self) -> Option<&Key> { match self { @@ -60,9 +63,10 @@ impl VNode { pub fn to_vlist_mut(&mut self) -> &mut VList { loop { match *self { - Self::VList(ref mut m) => return m, + Self::VList(ref mut m) => return Rc::make_mut(m), _ => { - *self = VNode::VList(VList::with_children(vec![mem::take(self)], None)); + *self = + VNode::VList(Rc::new(VList::with_children(vec![mem::take(self)], None))); } } } @@ -105,7 +109,7 @@ impl VNode { impl Default for VNode { fn default() -> Self { - VNode::VList(VList::default()) + VNode::VList(Rc::new(VList::default())) } } @@ -119,35 +123,35 @@ impl From for VNode { impl From for VNode { #[inline] fn from(vlist: VList) -> Self { - VNode::VList(vlist) + VNode::VList(Rc::new(vlist)) } } impl From for VNode { #[inline] fn from(vtag: VTag) -> Self { - VNode::VTag(Box::new(vtag)) + VNode::VTag(Rc::new(vtag)) } } impl From for VNode { #[inline] fn from(vcomp: VComp) -> Self { - VNode::VComp(vcomp) + VNode::VComp(Rc::new(vcomp)) } } impl From for VNode { #[inline] fn from(vsuspense: VSuspense) -> Self { - VNode::VSuspense(vsuspense) + VNode::VSuspense(Rc::new(vsuspense)) } } impl From for VNode { #[inline] fn from(vportal: VPortal) -> Self { - VNode::VPortal(vportal) + VNode::VPortal(Rc::new(vportal)) } } @@ -156,7 +160,7 @@ where COMP: BaseComponent, { fn from(vchild: VChild) -> Self { - VNode::VComp(VComp::from(vchild)) + VNode::VComp(Rc::new(VComp::from(vchild))) } } @@ -168,10 +172,10 @@ impl From for VNode { impl> FromIterator for VNode { fn from_iter>(iter: T) -> Self { - VNode::VList(VList::with_children( + VNode::VList(Rc::new(VList::with_children( iter.into_iter().map(|n| n.into()).collect(), None, - )) + ))) } } diff --git a/packages/yew/src/virtual_dom/vportal.rs b/packages/yew/src/virtual_dom/vportal.rs index b0b7b301f5a..ce39e82b9dd 100644 --- a/packages/yew/src/virtual_dom/vportal.rs +++ b/packages/yew/src/virtual_dom/vportal.rs @@ -8,10 +8,10 @@ use super::VNode; pub struct VPortal { /// The element under which the content is inserted. pub host: Element, - /// The next sibling after the inserted content. Most be a child of `host`. + /// The next sibling after the inserted content. Must be a child of `host`. pub inner_sibling: Option, /// The inserted node - pub node: Box, + pub node: VNode, } impl VPortal { @@ -20,7 +20,7 @@ impl VPortal { Self { host, inner_sibling: None, - node: Box::new(content), + node: content, } } @@ -31,7 +31,7 @@ impl VPortal { Self { host, inner_sibling, - node: Box::new(content), + node: content, } } } diff --git a/packages/yew/src/virtual_dom/vraw.rs b/packages/yew/src/virtual_dom/vraw.rs index 366e5248f00..9c8e7a3c46a 100644 --- a/packages/yew/src/virtual_dom/vraw.rs +++ b/packages/yew/src/virtual_dom/vraw.rs @@ -1,3 +1,4 @@ +use crate::html::ImplicitClone; use crate::AttrValue; /// A raw HTML string to be used in VDOM. @@ -6,6 +7,8 @@ pub struct VRaw { pub html: AttrValue, } +impl ImplicitClone for VRaw {} + impl From for VRaw { fn from(html: AttrValue) -> Self { Self { html } diff --git a/packages/yew/src/virtual_dom/vsuspense.rs b/packages/yew/src/virtual_dom/vsuspense.rs index 1ae97b8603a..8167f4fb2e1 100644 --- a/packages/yew/src/virtual_dom/vsuspense.rs +++ b/packages/yew/src/virtual_dom/vsuspense.rs @@ -1,23 +1,26 @@ use super::{Key, VNode}; +use crate::html::ImplicitClone; /// This struct represents a suspendable DOM fragment. #[derive(Clone, Debug, PartialEq)] pub struct VSuspense { /// Child nodes. - pub(crate) children: Box, + pub(crate) children: VNode, /// Fallback nodes when suspended. - pub(crate) fallback: Box, + pub(crate) fallback: VNode, /// Whether the current status is suspended. pub(crate) suspended: bool, /// The Key. pub(crate) key: Option, } +impl ImplicitClone for VSuspense {} + impl VSuspense { pub fn new(children: VNode, fallback: VNode, suspended: bool, key: Option) -> Self { Self { - children: children.into(), - fallback: fallback.into(), + children, + fallback, suspended, key, } diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index 266c1e8f339..60f70c53c14 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -1,6 +1,5 @@ //! This module contains the implementation of a virtual element node [VTag]. -use std::borrow::Cow; use std::cmp::PartialEq; use std::marker::PhantomData; use std::mem; @@ -10,7 +9,7 @@ use std::rc::Rc; use web_sys::{HtmlInputElement as InputElement, HtmlTextAreaElement as TextAreaElement}; use super::{ApplyAttributeAs, AttrValue, Attributes, Key, Listener, Listeners, VNode}; -use crate::html::{IntoPropValue, NodeRef}; +use crate::html::{ImplicitClone, IntoPropValue, NodeRef}; /// SVG namespace string used for creating svg elements pub const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg"; @@ -22,9 +21,17 @@ pub const MATHML_NAMESPACE: &str = "http://www.w3.org/1998/Math/MathML"; pub const HTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml"; /// Value field corresponding to an [Element]'s `value` property -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq)] pub(crate) struct Value(Option, PhantomData); +impl Clone for Value { + fn clone(&self) -> Self { + Self::new(self.0.clone()) + } +} + +impl ImplicitClone for Value {} + impl Default for Value { fn default() -> Self { Self::new(None) @@ -68,6 +75,8 @@ pub(crate) struct InputFields { pub(crate) checked: Option, } +impl ImplicitClone for InputFields {} + impl Deref for InputFields { type Target = Value; @@ -111,12 +120,14 @@ pub(crate) enum VTagInner { /// Fields for all other kinds of [VTag]s Other { /// A tag of the element. - tag: Cow<'static, str>, + tag: AttrValue, /// children of the element. children: VNode, }, } +impl ImplicitClone for VTagInner {} + /// A type for a virtual /// [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) /// representation. @@ -133,10 +144,12 @@ pub struct VTag { pub key: Option, } +impl ImplicitClone for VTag {} + impl VTag { /// Creates a new [VTag] instance with `tag` name (cannot be changed later in DOM). - pub fn new(tag: impl Into>) -> Self { - let tag: Cow<'static, str> = tag.into(); + pub fn new(tag: impl Into) -> Self { + let tag = tag.into(); Self::new_base( match &*tag.to_ascii_lowercase() { "input" => VTagInner::Input(Default::default()), @@ -226,7 +239,7 @@ impl VTag { #[doc(hidden)] #[allow(clippy::too_many_arguments)] pub fn __new_other( - tag: Cow<'static, str>, + tag: AttrValue, node_ref: NodeRef, key: Option, // at bottom for more readable macro-expanded coded diff --git a/packages/yew/src/virtual_dom/vtext.rs b/packages/yew/src/virtual_dom/vtext.rs index beb89acdad7..c1a3d5f38c0 100644 --- a/packages/yew/src/virtual_dom/vtext.rs +++ b/packages/yew/src/virtual_dom/vtext.rs @@ -3,6 +3,7 @@ use std::cmp::PartialEq; use super::AttrValue; +use crate::html::ImplicitClone; /// A type for a virtual /// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode) @@ -13,6 +14,8 @@ pub struct VText { pub text: AttrValue, } +impl ImplicitClone for VText {} + impl VText { /// Creates new virtual text node with a content. pub fn new(text: impl Into) -> Self {