Implement an internal DomSlot for positioning instead of NodeRef (#3048)

use instead of NodeRef, decoupling the two
fixes #3043

* implement internal DomSlot
* move DomSlot into submodule of dom_bundle
* hide behind feature csr
* add test cases
* write get in continuation style, this saves a clone
* private DomSlot::get
This commit is contained in:
WorldSEnder 2023-01-09 14:08:08 +00:00 committed by GitHub
parent 30a05fcf07
commit c5ffe601f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 757 additions and 750 deletions

View File

@ -5,11 +5,10 @@ use std::rc::Rc;
use web_sys::Element;
use crate::dom_bundle::BSubtree;
use crate::html::{BaseComponent, NodeRef, Scope, Scoped};
use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot};
use crate::html::{BaseComponent, Scope, Scoped};
/// An instance of an application.
#[cfg(feature = "csr")]
#[derive(Debug)]
pub struct AppHandle<COMP: BaseComponent> {
/// `Scope` holder
@ -38,8 +37,8 @@ where
app.scope.mount_in_place(
hosting_root,
host,
NodeRef::default(),
NodeRef::default(),
DomSlot::at_end(),
DynamicDomSlot::new_debug_trapped(),
props,
);
@ -58,7 +57,7 @@ where
skip_all,
)]
pub fn update(&mut self, new_props: COMP::Properties) {
self.scope.reuse(Rc::new(new_props), NodeRef::default())
self.scope.reuse(Rc::new(new_props), DomSlot::at_end())
}
/// Schedule the app for destruction
@ -115,11 +114,11 @@ mod feat_hydration {
hosting_root,
host.clone(),
&mut fragment,
NodeRef::default(),
DynamicDomSlot::new_debug_trapped(),
Rc::clone(&props),
);
#[cfg(debug_assertions)] // Fix trapped next_sibling at the root
app.scope.reuse(props, NodeRef::default());
app.scope.reuse(props, DomSlot::at_end());
// We remove all remaining nodes, this mimics the clear_element behaviour in
// mount_with_props.

View File

@ -6,18 +6,17 @@ use std::fmt;
use web_sys::Element;
use super::{BNode, BSubtree, Reconcilable, ReconcileTarget};
use super::{BNode, BSubtree, DomSlot, DynamicDomSlot, Reconcilable, ReconcileTarget};
use crate::html::{AnyScope, Scoped};
use crate::virtual_dom::{Key, VComp};
use crate::NodeRef;
/// A virtual component. Compare with [VComp].
pub(super) struct BComp {
type_id: TypeId,
scope: Box<dyn Scoped>,
// A internal NodeRef passed around to track this components position. This
// is "stable", i.e. does not change when reconciled.
internal_ref: NodeRef,
/// An internal [`DomSlot`] passed around to track this components position. This
/// will dynamically adjust when a lifecycle changes the render state of this component.
own_position: DynamicDomSlot,
key: Option<Key>,
}
@ -41,10 +40,10 @@ impl ReconcileTarget for BComp {
self.scope.destroy_boxed(parent_to_detach);
}
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
self.scope.shift_node(next_parent.clone(), next_sibling);
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
self.scope.shift_node(next_parent.clone(), slot);
self.internal_ref.clone()
self.own_position.to_position()
}
}
@ -56,29 +55,29 @@ impl Reconcilable for VComp {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
) -> (NodeRef, Self::Bundle) {
slot: DomSlot,
) -> (DomSlot, Self::Bundle) {
let VComp {
type_id,
mountable,
key,
..
} = self;
let internal_ref = NodeRef::default();
let internal_ref = DynamicDomSlot::new_debug_trapped();
let scope = mountable.mount(
root,
parent_scope,
parent.to_owned(),
slot,
internal_ref.clone(),
next_sibling,
);
(
internal_ref.clone(),
internal_ref.to_position(),
BComp {
type_id,
internal_ref,
own_position: internal_ref,
key,
scope,
},
@ -90,17 +89,17 @@ impl Reconcilable for VComp {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
bundle: &mut BNode,
) -> NodeRef {
) -> DomSlot {
match bundle {
// If the existing bundle is the same type, reuse it and update its properties
BNode::Comp(ref mut bcomp)
if self.type_id == bcomp.type_id && self.key == bcomp.key =>
{
self.reconcile(root, parent_scope, parent, next_sibling, bcomp)
self.reconcile(root, parent_scope, parent, slot, bcomp)
}
_ => self.replace(root, parent_scope, parent, next_sibling, bundle),
_ => self.replace(root, parent_scope, parent, slot, bundle),
}
}
@ -109,14 +108,14 @@ impl Reconcilable for VComp {
_root: &BSubtree,
_parent_scope: &AnyScope,
_parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
bcomp: &mut Self::Bundle,
) -> NodeRef {
) -> DomSlot {
let VComp { mountable, key, .. } = self;
bcomp.key = key;
mountable.reuse(bcomp.scope.borrow(), next_sibling);
bcomp.internal_ref.clone()
mountable.reuse(bcomp.scope.borrow(), slot);
bcomp.own_position.to_position()
}
}
@ -132,14 +131,14 @@ mod feat_hydration {
parent_scope: &AnyScope,
parent: &Element,
fragment: &mut Fragment,
) -> (NodeRef, Self::Bundle) {
) -> Self::Bundle {
let VComp {
type_id,
mountable,
key,
..
} = self;
let internal_ref = NodeRef::default();
let internal_ref = DynamicDomSlot::new_debug_trapped();
let scoped = mountable.hydrate(
root.clone(),
@ -149,15 +148,12 @@ mod feat_hydration {
fragment,
);
(
internal_ref.clone(),
BComp {
type_id,
scope: scoped,
internal_ref,
key,
},
)
BComp {
type_id,
scope: scoped,
own_position: internal_ref,
key,
}
}
}
}
@ -172,7 +168,7 @@ mod tests {
use super::*;
use crate::dom_bundle::Reconcilable;
use crate::virtual_dom::{Key, VChild, VNode};
use crate::{html, scheduler, Children, Component, Context, Html, NodeRef, Properties};
use crate::{html, scheduler, Children, Component, Context, Html, Properties};
wasm_bindgen_test_configure!(run_in_browser);
@ -208,12 +204,12 @@ mod tests {
let (root, scope, parent) = setup_parent();
let comp = html! { <Comp></Comp> };
let (_, mut bundle) = comp.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut bundle) = comp.attach(&root, &scope, &parent, DomSlot::at_end());
scheduler::start_now();
for _ in 0..10000 {
let node = html! { <Comp></Comp> };
node.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut bundle);
node.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut bundle);
scheduler::start_now();
}
}
@ -344,7 +340,7 @@ mod tests {
// clear parent
parent.set_inner_html("");
node.attach(root, scope, parent, NodeRef::default());
node.attach(root, scope, parent, DomSlot::at_end());
scheduler::start_now();
parent.inner_html()
}

View File

@ -7,9 +7,9 @@ use std::ops::Deref;
use web_sys::Element;
use super::{test_log, BNode, BSubtree};
use super::{test_log, BNode, BSubtree, DomSlot};
use crate::dom_bundle::{Reconcilable, ReconcileTarget};
use crate::html::{AnyScope, NodeRef};
use crate::html::AnyScope;
use crate::virtual_dom::{Key, VList, VNode, VText};
/// This struct represents a mounted [VList]
@ -36,7 +36,7 @@ struct NodeWriter<'s> {
root: &'s BSubtree,
parent_scope: &'s AnyScope,
parent: &'s Element,
next_sibling: NodeRef,
slot: DomSlot,
}
impl<'s> NodeWriter<'s> {
@ -44,48 +44,33 @@ impl<'s> NodeWriter<'s> {
fn add(self, node: VNode) -> (Self, BNode) {
test_log!("adding: {:?}", node);
test_log!(
" parent={:?}, next_sibling={:?}",
" parent={:?}, slot={:?}",
self.parent.outer_html(),
self.next_sibling
self.slot
);
let (next, bundle) =
node.attach(self.root, self.parent_scope, self.parent, self.next_sibling);
test_log!(" next_position: {:?}", next);
(
Self {
next_sibling: next,
..self
},
bundle,
)
let (next, bundle) = node.attach(self.root, self.parent_scope, self.parent, self.slot);
test_log!(" next_slot: {:?}", next);
(Self { slot: next, ..self }, bundle)
}
/// Shift a bundle into place without patching it
fn shift(&self, bundle: &mut BNode) {
bundle.shift(self.parent, self.next_sibling.clone());
bundle.shift(self.parent, self.slot.clone());
}
/// Patch a bundle with a new node
fn patch(self, node: VNode, bundle: &mut BNode) -> Self {
test_log!("patching: {:?} -> {:?}", bundle, node);
test_log!(
" parent={:?}, next_sibling={:?}",
" parent={:?}, slot={:?}",
self.parent.outer_html(),
self.next_sibling
self.slot
);
// Advance the next sibling reference (from right to left)
let next = node.reconcile_node(
self.root,
self.parent_scope,
self.parent,
self.next_sibling,
bundle,
);
let next =
node.reconcile_node(self.root, self.parent_scope, self.parent, self.slot, bundle);
test_log!(" next_position: {:?}", next);
Self {
next_sibling: next,
..self
}
Self { slot: next, ..self }
}
}
/// Helper struct implementing [Eq] and [Hash] by only looking at a node's key
@ -148,15 +133,15 @@ impl BList {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
lefts: Vec<VNode>,
rights: &mut Vec<BNode>,
) -> NodeRef {
) -> DomSlot {
let mut writer = NodeWriter {
root,
parent_scope,
parent,
next_sibling,
slot,
};
// Remove extra nodes
@ -178,7 +163,7 @@ impl BList {
rights.push(el);
writer = next_writer;
}
writer.next_sibling
writer.slot
}
/// Diff and patch fully keyed child lists.
@ -189,10 +174,10 @@ impl BList {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
left_vdoms: Vec<VNode>,
rev_bundles: &mut Vec<BNode>,
) -> NodeRef {
) -> DomSlot {
macro_rules! key {
($v:expr) => {
$v.key().expect("unkeyed child in fully keyed list")
@ -216,14 +201,7 @@ impl BList {
// Corresponds to adding or removing items from the back of the list
if matching_len_end == std::cmp::min(left_vdoms.len(), rev_bundles.len()) {
// No key changes
return Self::apply_unkeyed(
root,
parent_scope,
parent,
next_sibling,
left_vdoms,
rev_bundles,
);
return Self::apply_unkeyed(root, parent_scope, parent, slot, left_vdoms, rev_bundles);
}
// We partially drain the new vnodes in several steps.
@ -232,7 +210,7 @@ impl BList {
root,
parent_scope,
parent,
next_sibling,
slot,
};
// Step 1. Diff matching children at the end
let lefts_to = lefts.len() - matching_len_end;
@ -369,7 +347,7 @@ impl BList {
writer = writer.patch(l, r);
}
writer.next_sibling
writer.slot
}
}
@ -380,14 +358,12 @@ impl ReconcileTarget for BList {
}
}
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
let mut next_sibling = next_sibling;
fn shift(&self, next_parent: &Element, mut slot: DomSlot) -> DomSlot {
for node in self.rev_children.iter() {
next_sibling = node.shift(next_parent, next_sibling.clone());
slot = node.shift(next_parent, slot);
}
next_sibling
slot
}
}
@ -399,10 +375,10 @@ impl Reconcilable for VList {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
) -> (NodeRef, Self::Bundle) {
slot: DomSlot,
) -> (DomSlot, Self::Bundle) {
let mut self_ = BList::new();
let node_ref = self.reconcile(root, parent_scope, parent, next_sibling, &mut self_);
let node_ref = self.reconcile(root, parent_scope, parent, slot, &mut self_);
(node_ref, self_)
}
@ -411,13 +387,13 @@ impl Reconcilable for VList {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
bundle: &mut BNode,
) -> NodeRef {
) -> DomSlot {
// 'Forcefully' pretend the existing node is a list. Creates a
// singleton list if it isn't already.
let blist = bundle.make_list();
self.reconcile(root, parent_scope, parent, next_sibling, blist)
self.reconcile(root, parent_scope, parent, slot, blist)
}
fn reconcile(
@ -425,9 +401,9 @@ impl Reconcilable for VList {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
blist: &mut BList,
) -> NodeRef {
) -> DomSlot {
// Here, we will try to diff the previous list elements with the new
// ones we want to insert. For that, we will use two lists:
// - lefts: new elements to render in the DOM
@ -454,9 +430,9 @@ impl Reconcilable for VList {
rights.reserve_exact(additional);
}
let first = if fully_keyed && blist.fully_keyed {
BList::apply_keyed(root, parent_scope, parent, next_sibling, lefts, rights)
BList::apply_keyed(root, parent_scope, parent, slot, lefts, rights)
} else {
BList::apply_unkeyed(root, parent_scope, parent, next_sibling, lefts, rights)
BList::apply_unkeyed(root, parent_scope, parent, slot, lefts, rights)
};
blist.fully_keyed = fully_keyed;
blist.key = self.key;
@ -477,32 +453,24 @@ mod feat_hydration {
parent_scope: &AnyScope,
parent: &Element,
fragment: &mut Fragment,
) -> (NodeRef, Self::Bundle) {
let node_ref = NodeRef::default();
) -> Self::Bundle {
let fully_keyed = self.fully_keyed();
let vchildren = self.children;
let mut children = Vec::with_capacity(vchildren.len());
for (index, child) in vchildren.into_iter().enumerate() {
let (child_node_ref, child) = child.hydrate(root, parent_scope, parent, fragment);
if index == 0 {
node_ref.link(child_node_ref);
}
for child in vchildren.into_iter() {
let child = child.hydrate(root, parent_scope, parent, fragment);
children.push(child);
}
children.reverse();
(
node_ref,
BList {
rev_children: children,
fully_keyed,
key: self.key,
},
)
BList {
rev_children: children,
fully_keyed,
key: self.key,
}
}
}
}

View File

@ -4,9 +4,9 @@ use std::fmt;
use web_sys::{Element, Node};
use super::{BComp, BList, BPortal, BRaw, BSubtree, BSuspense, BTag, BText};
use super::{BComp, BList, BPortal, BRaw, BSubtree, BSuspense, BTag, BText, DomSlot};
use crate::dom_bundle::{Reconcilable, ReconcileTarget};
use crate::html::{AnyScope, NodeRef};
use crate::html::AnyScope;
use crate::virtual_dom::{Key, VNode};
/// The bundle implementation to [VNode].
@ -65,22 +65,20 @@ impl ReconcileTarget for BNode {
}
}
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
match self {
Self::Tag(ref vtag) => vtag.shift(next_parent, next_sibling),
Self::Text(ref btext) => btext.shift(next_parent, next_sibling),
Self::Comp(ref bsusp) => bsusp.shift(next_parent, next_sibling),
Self::List(ref vlist) => vlist.shift(next_parent, next_sibling),
Self::Tag(ref vtag) => vtag.shift(next_parent, slot),
Self::Text(ref btext) => btext.shift(next_parent, slot),
Self::Comp(ref bsusp) => bsusp.shift(next_parent, slot),
Self::List(ref vlist) => vlist.shift(next_parent, slot),
Self::Ref(ref node) => {
next_parent
.insert_before(node, next_sibling.get().as_ref())
.unwrap();
slot.insert(next_parent, node);
NodeRef::new(node.clone())
DomSlot::at(node.clone())
}
Self::Portal(ref vportal) => vportal.shift(next_parent, next_sibling),
Self::Suspense(ref vsuspense) => vsuspense.shift(next_parent, next_sibling),
Self::Raw(ref braw) => braw.shift(next_parent, next_sibling),
Self::Portal(ref vportal) => vportal.shift(next_parent, slot),
Self::Suspense(ref vsuspense) => vsuspense.shift(next_parent, slot),
Self::Raw(ref braw) => braw.shift(next_parent, slot),
}
}
}
@ -93,40 +91,39 @@ impl Reconcilable for VNode {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
) -> (NodeRef, Self::Bundle) {
slot: DomSlot,
) -> (DomSlot, Self::Bundle) {
match self {
VNode::VTag(vtag) => {
let (node_ref, tag) = vtag.attach(root, parent_scope, parent, next_sibling);
let (node_ref, tag) = vtag.attach(root, parent_scope, parent, slot);
(node_ref, tag.into())
}
VNode::VText(vtext) => {
let (node_ref, text) = vtext.attach(root, parent_scope, parent, next_sibling);
let (node_ref, text) = vtext.attach(root, parent_scope, parent, slot);
(node_ref, text.into())
}
VNode::VComp(vcomp) => {
let (node_ref, comp) = vcomp.attach(root, parent_scope, parent, next_sibling);
let (node_ref, comp) = vcomp.attach(root, parent_scope, parent, slot);
(node_ref, comp.into())
}
VNode::VList(vlist) => {
let (node_ref, list) = vlist.attach(root, parent_scope, parent, next_sibling);
let (node_ref, list) = vlist.attach(root, parent_scope, parent, slot);
(node_ref, list.into())
}
VNode::VRef(node) => {
super::insert_node(&node, parent, next_sibling.get().as_ref());
(NodeRef::new(node.clone()), BNode::Ref(node))
slot.insert(parent, &node);
(DomSlot::at(node.clone()), BNode::Ref(node))
}
VNode::VPortal(vportal) => {
let (node_ref, portal) = vportal.attach(root, parent_scope, parent, next_sibling);
let (node_ref, portal) = vportal.attach(root, parent_scope, parent, slot);
(node_ref, portal.into())
}
VNode::VSuspense(vsuspsense) => {
let (node_ref, suspsense) =
vsuspsense.attach(root, parent_scope, parent, next_sibling);
let (node_ref, suspsense) = vsuspsense.attach(root, parent_scope, parent, slot);
(node_ref, suspsense.into())
}
VNode::VRaw(vraw) => {
let (node_ref, raw) = vraw.attach(root, parent_scope, parent, next_sibling);
let (node_ref, raw) = vraw.attach(root, parent_scope, parent, slot);
(node_ref, raw.into())
}
}
@ -137,10 +134,10 @@ impl Reconcilable for VNode {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
bundle: &mut BNode,
) -> NodeRef {
self.reconcile(root, parent_scope, parent, next_sibling, bundle)
) -> DomSlot {
self.reconcile(root, parent_scope, parent, slot, bundle)
}
fn reconcile(
@ -148,46 +145,25 @@ impl Reconcilable for VNode {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
bundle: &mut BNode,
) -> NodeRef {
) -> DomSlot {
match self {
VNode::VTag(vtag) => {
vtag.reconcile_node(root, parent_scope, parent, next_sibling, bundle)
}
VNode::VText(vtext) => {
vtext.reconcile_node(root, parent_scope, parent, next_sibling, bundle)
}
VNode::VComp(vcomp) => {
vcomp.reconcile_node(root, parent_scope, parent, next_sibling, bundle)
}
VNode::VList(vlist) => {
vlist.reconcile_node(root, parent_scope, parent, next_sibling, bundle)
}
VNode::VRef(node) => {
let _existing = match bundle {
BNode::Ref(ref n) if &node == n => n,
_ => {
return VNode::VRef(node).replace(
root,
parent_scope,
parent,
next_sibling,
bundle,
);
}
};
NodeRef::new(node)
}
VNode::VTag(vtag) => 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::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, next_sibling, bundle)
vportal.reconcile_node(root, parent_scope, parent, slot, bundle)
}
VNode::VSuspense(vsuspsense) => {
vsuspsense.reconcile_node(root, parent_scope, parent, next_sibling, bundle)
}
VNode::VRaw(vraw) => {
vraw.reconcile_node(root, parent_scope, parent, next_sibling, bundle)
vsuspsense.reconcile_node(root, parent_scope, parent, slot, bundle)
}
VNode::VRaw(vraw) => vraw.reconcile_node(root, parent_scope, parent, slot, bundle),
}
}
}
@ -268,24 +244,12 @@ mod feat_hydration {
parent_scope: &AnyScope,
parent: &Element,
fragment: &mut Fragment,
) -> (NodeRef, Self::Bundle) {
) -> Self::Bundle {
match self {
VNode::VTag(vtag) => {
let (node_ref, tag) = vtag.hydrate(root, parent_scope, parent, fragment);
(node_ref, tag.into())
}
VNode::VText(vtext) => {
let (node_ref, text) = vtext.hydrate(root, parent_scope, parent, fragment);
(node_ref, text.into())
}
VNode::VComp(vcomp) => {
let (node_ref, comp) = vcomp.hydrate(root, parent_scope, parent, fragment);
(node_ref, comp.into())
}
VNode::VList(vlist) => {
let (node_ref, list) = vlist.hydrate(root, parent_scope, parent, fragment);
(node_ref, list.into())
}
VNode::VTag(vtag) => 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(),
// You cannot hydrate a VRef.
VNode::VRef(_) => {
panic!(
@ -300,11 +264,9 @@ mod feat_hydration {
use_effect."
)
}
VNode::VSuspense(vsuspense) => {
let (node_ref, suspense) =
vsuspense.hydrate(root, parent_scope, parent, fragment);
(node_ref, suspense.into())
}
VNode::VSuspense(vsuspense) => vsuspense
.hydrate(root, parent_scope, parent, fragment)
.into(),
VNode::VRaw(_) => {
panic!("VRaw is not hydratable (raw HTML string cannot be hydrated)")
}

View File

@ -1,10 +1,10 @@
//! This module contains the bundle implementation of a portal [BPortal].
use web_sys::Element;
use web_sys::{Element, Node};
use super::{test_log, BNode, BSubtree};
use super::{test_log, BNode, BSubtree, DomSlot};
use crate::dom_bundle::{Reconcilable, ReconcileTarget};
use crate::html::{AnyScope, NodeRef};
use crate::html::AnyScope;
use crate::virtual_dom::{Key, VPortal};
/// The bundle implementation to [VPortal].
@ -15,7 +15,7 @@ pub struct BPortal {
/// The element under which the content is inserted.
host: Element,
/// The next sibling after the inserted content
inner_sibling: NodeRef,
inner_sibling: Option<Node>,
/// The inserted node
node: Box<BNode>,
}
@ -26,10 +26,9 @@ impl ReconcileTarget for BPortal {
self.node.detach(&self.inner_root, &self.host, false);
}
fn shift(&self, _next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
// portals have nothing in it's original place of DOM, we also do nothing.
next_sibling
fn shift(&self, _next_parent: &Element, slot: DomSlot) -> DomSlot {
// portals have nothing in its original place of DOM, we also do nothing.
slot
}
}
@ -41,22 +40,18 @@ impl Reconcilable for VPortal {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
host_next_sibling: NodeRef,
) -> (NodeRef, Self::Bundle) {
host_slot: DomSlot,
) -> (DomSlot, Self::Bundle) {
let Self {
host,
inner_sibling,
node,
} = self;
let inner_sibling = {
let sib_ref = NodeRef::default();
sib_ref.set(inner_sibling);
sib_ref
};
let inner_slot = DomSlot::create(inner_sibling.clone());
let inner_root = root.create_subroot(parent.clone(), &host);
let (_, inner) = node.attach(&inner_root, parent_scope, &host, inner_sibling.clone());
let (_, inner) = node.attach(&inner_root, parent_scope, &host, inner_slot);
(
host_next_sibling,
host_slot,
BPortal {
inner_root,
host,
@ -71,14 +66,12 @@ impl Reconcilable for VPortal {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
bundle: &mut BNode,
) -> NodeRef {
) -> DomSlot {
match bundle {
BNode::Portal(portal) => {
self.reconcile(root, parent_scope, parent, next_sibling, portal)
}
_ => self.replace(root, parent_scope, parent, next_sibling, bundle),
BNode::Portal(portal) => self.reconcile(root, parent_scope, parent, slot, portal),
_ => self.replace(root, parent_scope, parent, slot, bundle),
}
}
@ -87,9 +80,9 @@ impl Reconcilable for VPortal {
_root: &BSubtree,
parent_scope: &AnyScope,
_parent: &Element,
next_sibling: NodeRef,
host_slot: DomSlot,
portal: &mut Self::Bundle,
) -> NodeRef {
) -> DomSlot {
let Self {
host,
inner_sibling,
@ -98,23 +91,23 @@ impl Reconcilable for VPortal {
let old_host = std::mem::replace(&mut portal.host, host);
let should_shift = old_host != portal.host || portal.inner_sibling.get() != inner_sibling;
portal.inner_sibling.set(inner_sibling);
let should_shift = old_host != portal.host || portal.inner_sibling != inner_sibling;
portal.inner_sibling = inner_sibling;
let inner_slot = DomSlot::create(portal.inner_sibling.clone());
if should_shift {
// Remount the inner node somewhere else instead of diffing
// Move the node, but keep the state
let inner_sibling = portal.inner_sibling.clone();
portal.node.shift(&portal.host, inner_sibling);
portal.node.shift(&portal.host, inner_slot.clone());
}
node.reconcile_node(
&portal.inner_root,
parent_scope,
&portal.host,
portal.inner_sibling.clone(),
inner_slot,
&mut portal.node,
);
next_sibling
host_slot
}
}
@ -136,6 +129,7 @@ mod layout_tests {
use yew::virtual_dom::VPortal;
use super::*;
use crate::html::NodeRef;
use crate::tests::layout_tests::{diff_layouts, TestLayout};
use crate::virtual_dom::VNode;
use crate::{create_portal, html};
@ -252,13 +246,13 @@ mod layout_tests {
);
let (_, mut bundle) = portal
.clone()
.attach(&root, &scope, &parent, NodeRef::default());
.attach(&root, &scope, &parent, DomSlot::at_end());
// Focus the input, then reconcile again
let input_el = input_ref.cast::<HtmlInputElement>().unwrap();
input_el.focus().unwrap();
let _ = portal.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut bundle);
let _ = portal.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut bundle);
let new_input_el = input_ref.cast::<HtmlInputElement>().unwrap();
assert_eq!(input_el, new_input_el);

View File

@ -1,26 +1,23 @@
use wasm_bindgen::JsCast;
use web_sys::Element;
use web_sys::{Element, Node};
use crate::dom_bundle::bnode::BNode;
use crate::dom_bundle::traits::{Reconcilable, ReconcileTarget};
use crate::dom_bundle::utils::insert_node;
use crate::dom_bundle::BSubtree;
use super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
use crate::html::AnyScope;
use crate::virtual_dom::VRaw;
use crate::{AttrValue, NodeRef};
use crate::AttrValue;
#[derive(Debug)]
pub struct BRaw {
reference: NodeRef,
reference: Option<Node>,
children_count: usize,
html: AttrValue,
}
impl BRaw {
fn create_elements(html: &str) -> Vec<Element> {
fn create_elements(html: &str) -> Vec<Node> {
let div = gloo::utils::document().create_element("div").unwrap();
div.set_inner_html(html);
let children = div.children();
let children = div.child_nodes();
let children = js_sys::Array::from(&children);
let children = children.to_vec();
children
@ -30,7 +27,7 @@ impl BRaw {
}
fn detach_bundle(&self, parent: &Element) {
let mut next_node = self.reference.get();
let mut next_node = self.reference.clone();
for _ in 0..self.children_count {
if let Some(node) = next_node {
next_node = node.next_sibling();
@ -38,6 +35,13 @@ impl BRaw {
}
}
}
fn position(&self, next_slot: DomSlot) -> DomSlot {
self.reference
.as_ref()
.map(|n| DomSlot::at(n.clone()))
.unwrap_or(next_slot)
}
}
impl ReconcileTarget for BRaw {
@ -45,29 +49,15 @@ impl ReconcileTarget for BRaw {
self.detach_bundle(parent);
}
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
let mut next_node = match self.reference.get() {
Some(n) => n,
None => return NodeRef::default(),
};
let insert = |n| {
next_parent
.insert_before(&n, next_sibling.get().as_ref())
.unwrap()
};
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
let mut next_node = self.reference.clone();
for _ in 0..self.children_count {
let current = next_node;
next_node = match current.next_sibling() {
Some(n) => n,
None => {
// if nothing is next, add whatever is the current node and return early
insert(current.clone());
return NodeRef::new(current);
}
};
insert(current);
if let Some(node) = next_node {
next_node = node.next_sibling();
slot.insert(next_parent, &node);
}
}
NodeRef::new(next_node)
self.position(slot)
}
}
@ -79,37 +69,24 @@ impl Reconcilable for VRaw {
_root: &BSubtree,
_parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
) -> (NodeRef, Self::Bundle) {
slot: DomSlot,
) -> (DomSlot, Self::Bundle) {
let elements = BRaw::create_elements(&self.html);
if elements.is_empty() {
return (
next_sibling.clone(),
BRaw {
reference: next_sibling,
children_count: 0,
html: self.html,
},
);
}
let node_ref = NodeRef::default();
let count = elements.len();
let mut iter = elements.into_iter();
let first = iter.next().unwrap();
insert_node(&first, parent, next_sibling.get().as_ref());
node_ref.set(Some(first.into()));
for child in iter {
insert_node(&child, parent, next_sibling.get().as_ref());
let reference = iter.next();
if let Some(ref first) = reference {
slot.insert(parent, first);
for ref child in iter {
slot.insert(parent, child);
}
}
(
node_ref.clone(),
BRaw {
reference: node_ref,
children_count: count,
html: self.html,
},
)
let this = BRaw {
reference,
children_count: count,
html: self.html,
};
(this.position(slot), this)
}
fn reconcile_node(
@ -117,13 +94,13 @@ impl Reconcilable for VRaw {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
bundle: &mut BNode,
) -> NodeRef {
) -> DomSlot {
match bundle {
BNode::Raw(raw) if raw.html == self.html => raw.reference.clone(),
BNode::Raw(raw) => self.reconcile(root, parent_scope, parent, next_sibling, raw),
_ => self.replace(root, parent_scope, parent, next_sibling, bundle),
BNode::Raw(raw) if raw.html == self.html => raw.position(slot),
BNode::Raw(raw) => self.reconcile(root, parent_scope, parent, slot, raw),
_ => self.replace(root, parent_scope, parent, slot, bundle),
}
}
@ -132,18 +109,18 @@ impl Reconcilable for VRaw {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
bundle: &mut Self::Bundle,
) -> NodeRef {
) -> DomSlot {
if self.html != bundle.html {
// we don't have a way to diff what's changed in the string so we remove the node and
// reattach it
bundle.detach_bundle(parent);
let (node_ref, braw) = self.attach(root, parent_scope, parent, next_sibling);
let (node_ref, braw) = self.attach(root, parent_scope, parent, slot);
*bundle = braw;
node_ref
} else {
bundle.reference.clone()
bundle.position(slot)
}
}
}
@ -166,7 +143,7 @@ mod tests {
const HTML: &str = "<span>text</span>";
let elem = VNode::from_html_unchecked(HTML.into());
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
assert_braw(&mut elem);
assert_eq!(parent.inner_html(), HTML)
}
@ -177,7 +154,7 @@ mod tests {
const HTML: &str = "";
let elem = VNode::from_html_unchecked(HTML.into());
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
assert_braw(&mut elem);
assert_eq!(parent.inner_html(), HTML)
}
@ -189,7 +166,7 @@ mod tests {
const HTML: &str =
r#"<p>one <a href="https://yew.rs">link</a> more paragraph</p><div>here</div>"#;
let elem = VNode::from_html_unchecked(HTML.into());
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
assert_braw(&mut elem);
assert_eq!(parent.inner_html(), HTML)
}
@ -199,7 +176,7 @@ mod tests {
const HTML: &str = r#"<p>paragraph</p><a href="https://yew.rs">link</a>"#;
let elem = VNode::from_html_unchecked(HTML.into());
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
assert_braw(&mut elem);
assert_eq!(parent.inner_html(), HTML)
}
@ -210,7 +187,7 @@ mod tests {
const HTML: &str = r#"<p>paragraph</p><a href="https://yew.rs">link</a>"#;
let elem = VNode::from_html_unchecked(HTML.into());
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
assert_braw(&mut elem);
assert_eq!(parent.inner_html(), HTML);
elem.detach(&root, &parent, false);
@ -223,7 +200,7 @@ mod tests {
const HTML: &str = r#"<p>paragraph</p>"#;
let elem = VNode::from_html_unchecked(HTML.into());
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
assert_braw(&mut elem);
assert_eq!(parent.inner_html(), HTML);
elem.detach(&root, &parent, false);
@ -236,7 +213,7 @@ mod tests {
const HTML: &str = "";
let elem = VNode::from_html_unchecked(HTML.into());
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
assert_braw(&mut elem);
assert_eq!(parent.inner_html(), HTML);
elem.detach(&root, &parent, false);
@ -332,14 +309,14 @@ mod tests {
const HTML: &str = r#"<p>paragraph</p>"#;
let elem = VNode::from_html_unchecked(HTML.into());
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
assert_braw(&mut elem);
assert_eq!(parent.inner_html(), HTML);
let new_parent = document().create_element("section").unwrap();
document().body().unwrap().append_child(&parent).unwrap();
elem.shift(&new_parent, NodeRef::default());
elem.shift(&new_parent, DomSlot::at_end());
assert_eq!(new_parent.inner_html(), HTML);
assert_eq!(parent.inner_html(), "");
@ -360,7 +337,7 @@ mod tests {
let new_sibling = document().create_text_node(SIBLING_CONTENT);
new_parent.append_child(&new_sibling).unwrap();
let new_sibling_ref = NodeRef::new(new_sibling.into());
let new_sibling_ref = DomSlot::at(new_sibling.into());
elem.shift(&new_parent, new_sibling_ref);
@ -378,14 +355,14 @@ mod tests {
const HTML: &str = r#"<p>paragraph</p><a href="https://yew.rs">link</a>"#;
let elem = VNode::from_html_unchecked(HTML.into());
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
assert_braw(&mut elem);
assert_eq!(parent.inner_html(), HTML);
let new_parent = document().create_element("section").unwrap();
document().body().unwrap().append_child(&parent).unwrap();
elem.shift(&new_parent, NodeRef::default());
elem.shift(&new_parent, DomSlot::at_end());
assert_eq!(parent.inner_html(), "");
assert_eq!(new_parent.inner_html(), HTML);

View File

@ -5,10 +5,9 @@ use web_sys::Element;
#[cfg(feature = "hydration")]
use super::Fragment;
use super::{BNode, BSubtree, Reconcilable, ReconcileTarget};
use super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
use crate::html::AnyScope;
use crate::virtual_dom::{Key, VSuspense};
use crate::NodeRef;
#[derive(Debug)]
enum Fallback {
@ -60,12 +59,12 @@ impl ReconcileTarget for BSuspense {
}
}
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
match self.fallback.as_ref() {
Some(Fallback::Bundle(bundle)) => bundle.shift(next_parent, next_sibling),
Some(Fallback::Bundle(bundle)) => bundle.shift(next_parent, slot),
#[cfg(feature = "hydration")]
Some(Fallback::Fragment(fragment)) => fragment.shift(next_parent, next_sibling),
None => self.children_bundle.shift(next_parent, next_sibling),
Some(Fallback::Fragment(fragment)) => fragment.shift(next_parent, slot),
None => self.children_bundle.shift(next_parent, slot),
}
}
}
@ -78,8 +77,8 @@ impl Reconcilable for VSuspense {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
) -> (NodeRef, Self::Bundle) {
slot: DomSlot,
) -> (DomSlot, Self::Bundle) {
let VSuspense {
children,
fallback,
@ -94,9 +93,8 @@ impl Reconcilable for VSuspense {
// tree while rendering fallback UI into the original place where children resides in.
if suspended {
let (_child_ref, children_bundle) =
children.attach(root, parent_scope, &detached_parent, NodeRef::default());
let (fallback_ref, fallback) =
fallback.attach(root, parent_scope, parent, next_sibling);
children.attach(root, parent_scope, &detached_parent, DomSlot::at_end());
let (fallback_ref, fallback) = fallback.attach(root, parent_scope, parent, slot);
(
fallback_ref,
BSuspense {
@ -107,8 +105,7 @@ impl Reconcilable for VSuspense {
},
)
} else {
let (child_ref, children_bundle) =
children.attach(root, parent_scope, parent, next_sibling);
let (child_ref, children_bundle) = children.attach(root, parent_scope, parent, slot);
(
child_ref,
BSuspense {
@ -126,15 +123,15 @@ impl Reconcilable for VSuspense {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
bundle: &mut BNode,
) -> NodeRef {
) -> DomSlot {
match bundle {
// We only preserve the child state if they are the same suspense.
BNode::Suspense(m) if m.key == self.key => {
self.reconcile(root, parent_scope, parent, next_sibling, m)
self.reconcile(root, parent_scope, parent, slot, m)
}
_ => self.replace(root, parent_scope, parent, next_sibling, bundle),
_ => self.replace(root, parent_scope, parent, slot, bundle),
}
}
@ -143,9 +140,9 @@ impl Reconcilable for VSuspense {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
suspense: &mut Self::Bundle,
) -> NodeRef {
) -> DomSlot {
let VSuspense {
children,
fallback: vfallback,
@ -165,41 +162,40 @@ impl Reconcilable for VSuspense {
root,
parent_scope,
&suspense.detached_parent,
NodeRef::default(),
DomSlot::at_end(),
children_bundle,
);
match fallback {
Fallback::Bundle(bundle) => {
vfallback.reconcile_node(root, parent_scope, parent, next_sibling, bundle)
vfallback.reconcile_node(root, parent_scope, parent, slot, bundle)
}
#[cfg(feature = "hydration")]
Fallback::Fragment(fragment) => match fragment.front().cloned() {
Some(m) => NodeRef::new(m),
None => next_sibling,
Some(m) => DomSlot::at(m),
None => slot,
},
}
}
// Not suspended, just reconcile the children into the DOM
(false, None) => {
children.reconcile_node(root, parent_scope, parent, next_sibling, children_bundle)
children.reconcile_node(root, parent_scope, parent, slot, children_bundle)
}
// Freshly suspended. Shift children into the detached parent, then add fallback to the
// DOM
(true, None) => {
children_bundle.shift(&suspense.detached_parent, NodeRef::default());
children_bundle.shift(&suspense.detached_parent, DomSlot::at_end());
children.reconcile_node(
root,
parent_scope,
&suspense.detached_parent,
NodeRef::default(),
DomSlot::at_end(),
children_bundle,
);
// first render of fallback
let (fallback_ref, fallback) =
vfallback.attach(root, parent_scope, parent, next_sibling);
let (fallback_ref, fallback) = vfallback.attach(root, parent_scope, parent, slot);
suspense.fallback = Some(Fallback::Bundle(fallback));
fallback_ref
}
@ -218,8 +214,8 @@ impl Reconcilable for VSuspense {
}
};
children_bundle.shift(parent, next_sibling.clone());
children.reconcile_node(root, parent_scope, parent, next_sibling, children_bundle)
children_bundle.shift(parent, slot.clone());
children.reconcile_node(root, parent_scope, parent, slot, children_bundle)
}
}
}
@ -238,7 +234,7 @@ mod feat_hydration {
parent_scope: &AnyScope,
parent: &Element,
fragment: &mut Fragment,
) -> (NodeRef, Self::Bundle) {
) -> Self::Bundle {
let detached_parent = document()
.create_element("div")
.expect("failed to create detached element");
@ -254,33 +250,24 @@ mod feat_hydration {
// Even if initially suspended, these children correspond to the first non-suspended
// content Refer to VSuspense::render_to_string
let (_, children_bundle) =
let children_bundle =
self.children
.hydrate(root, parent_scope, &detached_parent, &mut nodes);
// We trim all leading text nodes before checking as it's likely these are whitespaces.
nodes.trim_start_text_nodes(&detached_parent);
nodes.trim_start_text_nodes();
assert!(nodes.is_empty(), "expected end of suspense, found node.");
let node_ref = fallback_fragment
.front()
.cloned()
.map(NodeRef::new)
.unwrap_or_default();
BSuspense {
children_bundle,
detached_parent,
key: self.key,
(
node_ref,
BSuspense {
children_bundle,
detached_parent,
key: self.key,
// We start hydration with the BSuspense being suspended.
// A subsequent render will resume the BSuspense if not needed to be suspended.
fallback: Some(Fallback::Fragment(fallback_fragment)),
},
)
// We start hydration with the BSuspense being suspended.
// A subsequent render will resume the BSuspense if not needed to be suspended.
fallback: Some(Fallback::Fragment(fallback_fragment)),
}
}
}
}

View File

@ -13,7 +13,7 @@ pub use listeners::Registry;
use wasm_bindgen::JsCast;
use web_sys::{Element, HtmlTextAreaElement as TextAreaElement};
use super::{insert_node, BList, BNode, BSubtree, Reconcilable, ReconcileTarget};
use super::{BList, BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
use crate::html::AnyScope;
use crate::virtual_dom::vtag::{InputFields, VTagInner, Value, SVG_NAMESPACE};
use crate::virtual_dom::{Attributes, Key, VTag};
@ -93,12 +93,10 @@ impl ReconcileTarget for BTag {
}
}
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
next_parent
.insert_before(&self.reference, next_sibling.get().as_ref())
.unwrap();
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
slot.insert(next_parent, &self.reference);
self.node_ref.clone()
DomSlot::at(self.reference.clone().into())
}
}
@ -110,8 +108,8 @@ impl Reconcilable for VTag {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
) -> (NodeRef, Self::Bundle) {
slot: DomSlot,
) -> (DomSlot, Self::Bundle) {
let el = self.create_element(parent);
let Self {
listeners,
@ -120,7 +118,7 @@ impl Reconcilable for VTag {
key,
..
} = self;
insert_node(&el, parent, next_sibling.get().as_ref());
slot.insert(parent, &el);
let attributes = attributes.apply(root, &el);
let listeners = listeners.apply(root, &el);
@ -135,14 +133,13 @@ impl Reconcilable for VTag {
BTagInner::Textarea { value }
}
VTagInner::Other { children, tag } => {
let (_, child_bundle) =
children.attach(root, parent_scope, &el, NodeRef::default());
let (_, child_bundle) = children.attach(root, parent_scope, &el, DomSlot::at_end());
BTagInner::Other { child_bundle, tag }
}
};
node_ref.set(Some(el.clone().into()));
(
node_ref.clone(),
DomSlot::at(el.clone().into()),
BTag {
inner,
listeners,
@ -159,9 +156,9 @@ impl Reconcilable for VTag {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
bundle: &mut BNode,
) -> NodeRef {
) -> DomSlot {
// This kind of branching patching routine reduces branch predictor misses and the need to
// unpack the enums (including `Option`s) all the time, resulting in a more streamlined
// patching flow
@ -179,18 +176,12 @@ impl Reconcilable for VTag {
}
_ => false,
} {
return self.reconcile(
root,
parent_scope,
parent,
next_sibling,
ex.deref_mut(),
);
return self.reconcile(root, parent_scope, parent, slot, ex.deref_mut());
}
}
_ => {}
};
self.replace(root, parent_scope, parent, next_sibling, bundle)
self.replace(root, parent_scope, parent, slot, bundle)
}
fn reconcile(
@ -198,9 +189,9 @@ impl Reconcilable for VTag {
root: &BSubtree,
parent_scope: &AnyScope,
_parent: &Element,
_next_sibling: NodeRef,
_slot: DomSlot,
tag: &mut Self::Bundle,
) -> NodeRef {
) -> DomSlot {
let el = &tag.reference;
self.attributes.apply_diff(root, el, &mut tag.attributes);
self.listeners.apply_diff(root, el, &mut tag.listeners);
@ -218,7 +209,7 @@ impl Reconcilable for VTag {
child_bundle: old, ..
},
) => {
new.reconcile(root, parent_scope, el, NodeRef::default(), old);
new.reconcile(root, parent_scope, el, DomSlot::at_end(), old);
}
// Can not happen, because we checked for tag equability above
_ => unsafe { unreachable_unchecked() },
@ -234,7 +225,7 @@ impl Reconcilable for VTag {
tag.node_ref.set(Some(el.clone().into()));
}
tag.node_ref.clone()
DomSlot::at(el.clone().into())
}
}
@ -302,9 +293,9 @@ mod feat_hydration {
self,
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
_parent: &Element,
fragment: &mut Fragment,
) -> (NodeRef, Self::Bundle) {
) -> Self::Bundle {
let tag_name = self.tag().to_owned();
let Self {
@ -316,7 +307,7 @@ mod feat_hydration {
} = self;
// We trim all text nodes as it's likely these are whitespaces.
fragment.trim_start_text_nodes(parent);
fragment.trim_start_text_nodes();
let node = fragment
.pop_front()
@ -355,9 +346,9 @@ mod feat_hydration {
}
VTagInner::Other { children, tag } => {
let mut nodes = Fragment::collect_children(&el);
let (_, child_bundle) = children.hydrate(root, parent_scope, &el, &mut nodes);
let child_bundle = children.hydrate(root, parent_scope, &el, &mut nodes);
nodes.trim_start_text_nodes(parent);
nodes.trim_start_text_nodes();
assert!(nodes.is_empty(), "expected EOF, found node.");
@ -367,17 +358,14 @@ mod feat_hydration {
node_ref.set(Some((*el).clone()));
(
node_ref.clone(),
BTag {
inner,
listeners,
attributes,
reference: el,
node_ref,
key,
},
)
BTag {
inner,
listeners,
attributes,
reference: el,
node_ref,
key,
}
}
}
}
@ -586,17 +574,17 @@ mod tests {
let svg_node = html! { <svg>{path_node}</svg> };
let svg_tag = assert_vtag(svg_node);
let (_, svg_tag) = svg_tag.attach(&root, &scope, &parent, NodeRef::default());
let (_, svg_tag) = svg_tag.attach(&root, &scope, &parent, DomSlot::at_end());
assert_namespace(&svg_tag, SVG_NAMESPACE);
let path_tag = assert_btag_ref(svg_tag.children().get(0).unwrap());
assert_namespace(path_tag, SVG_NAMESPACE);
let g_tag = assert_vtag(g_node.clone());
let (_, g_tag) = g_tag.attach(&root, &scope, &parent, NodeRef::default());
let (_, g_tag) = g_tag.attach(&root, &scope, &parent, DomSlot::at_end());
assert_namespace(&g_tag, HTML_NAMESPACE);
let g_tag = assert_vtag(g_node);
let (_, g_tag) = g_tag.attach(&root, &scope, &svg_el, NodeRef::default());
let (_, g_tag) = g_tag.attach(&root, &scope, &svg_el, DomSlot::at_end());
assert_namespace(&g_tag, SVG_NAMESPACE);
}
@ -695,7 +683,7 @@ mod tests {
let (root, scope, parent) = setup_parent();
let elem = html! { <div></div> };
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
let vtag = assert_btag_mut(&mut elem);
// test if the className has not been set
assert!(!vtag.reference().has_attribute("class"));
@ -705,7 +693,7 @@ mod tests {
let (root, scope, parent) = setup_parent();
let elem = gen_html();
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
let vtag = assert_btag_mut(&mut elem);
// test if the className has been set
assert!(vtag.reference().has_attribute("class"));
@ -729,7 +717,7 @@ mod tests {
// Initial state
let elem = html! { <input value={expected} /> };
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
let vtag = assert_btag_ref(&elem);
// User input
@ -741,7 +729,7 @@ mod tests {
let elem_vtag = assert_vtag(next_elem);
// Sync happens here
elem_vtag.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut elem);
elem_vtag.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut elem);
let vtag = assert_btag_ref(&elem);
// Get new current value of the input element
@ -760,7 +748,7 @@ mod tests {
// Initial state
let elem = html! { <input /> };
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
let vtag = assert_btag_ref(&elem);
// User input
@ -772,7 +760,7 @@ mod tests {
let elem_vtag = assert_vtag(next_elem);
// Value should not be refreshed
elem_vtag.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut elem);
elem_vtag.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut elem);
let vtag = assert_btag_ref(&elem);
// Get user value of the input element
@ -799,7 +787,7 @@ mod tests {
builder
}/> };
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
let vtag = assert_btag_mut(&mut elem);
// make sure the new tag name is used internally
assert_eq!(vtag.tag(), "a");
@ -857,7 +845,7 @@ mod tests {
let node_ref = NodeRef::default();
let elem: VNode = html! { <div ref={node_ref.clone()}></div> };
assert_vtag_ref(&elem);
let (_, elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
assert_eq!(node_ref.get(), parent.first_child());
elem.detach(&root, &parent, false);
assert!(node_ref.get().is_none());
@ -869,14 +857,14 @@ mod tests {
let node_ref_a = NodeRef::default();
let elem_a = html! { <div id="a" ref={node_ref_a.clone()} /> };
let (_, mut elem) = elem_a.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem_a.attach(&root, &scope, &parent, DomSlot::at_end());
// save the Node to check later that it has been reused.
let node_a = node_ref_a.get().unwrap();
let node_ref_b = NodeRef::default();
let elem_b = html! { <div id="b" ref={node_ref_b.clone()} /> };
elem_b.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut elem);
elem_b.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut elem);
let node_b = node_ref_b.get().unwrap();
@ -906,8 +894,8 @@ mod tests {
// The point of this diff is to first render the "after" div and then detach the "before"
// div, while both should be bound to the same node ref
let (_, mut elem) = before.attach(&root, &scope, &parent, NodeRef::default());
after.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut elem);
let (_, mut elem) = before.attach(&root, &scope, &parent, DomSlot::at_end());
after.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut elem);
assert_eq!(
test_ref
@ -938,7 +926,7 @@ mod tests {
let elem = VNode::VTag(Box::new(vtag));
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
// Create <div tabindex="0"> (removed first attribute "disabled")
let mut vtag = VTag::new("div");
@ -949,7 +937,7 @@ mod tests {
// Sync happens here
// this should remove the the "disabled" attribute
elem_vtag.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut elem);
elem_vtag.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut elem);
assert_eq!(
test_ref

View File

@ -3,10 +3,9 @@
use gloo::utils::document;
use web_sys::{Element, Text as TextNode};
use super::{insert_node, BNode, BSubtree, Reconcilable, ReconcileTarget};
use super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
use crate::html::AnyScope;
use crate::virtual_dom::{AttrValue, VText};
use crate::NodeRef;
/// The bundle implementation to [VText]
pub(super) struct BText {
@ -25,14 +24,10 @@ impl ReconcileTarget for BText {
}
}
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
let node = &self.text_node;
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
slot.insert(next_parent, &self.text_node);
next_parent
.insert_before(node, next_sibling.get().as_ref())
.unwrap();
NodeRef::new(self.text_node.clone().into())
DomSlot::at(self.text_node.clone().into())
}
}
@ -44,12 +39,12 @@ impl Reconcilable for VText {
_root: &BSubtree,
_parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
) -> (NodeRef, Self::Bundle) {
slot: DomSlot,
) -> (DomSlot, Self::Bundle) {
let Self { text } = self;
let text_node = document().create_text_node(&text);
insert_node(&text_node, parent, next_sibling.get().as_ref());
let node_ref = NodeRef::new(text_node.clone().into());
slot.insert(parent, &text_node);
let node_ref = DomSlot::at(text_node.clone().into());
(node_ref, BText { text, text_node })
}
@ -59,12 +54,12 @@ impl Reconcilable for VText {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
bundle: &mut BNode,
) -> NodeRef {
) -> DomSlot {
match bundle {
BNode::Text(btext) => self.reconcile(root, parent_scope, parent, next_sibling, btext),
_ => self.replace(root, parent_scope, parent, next_sibling, bundle),
BNode::Text(btext) => self.reconcile(root, parent_scope, parent, slot, btext),
_ => self.replace(root, parent_scope, parent, slot, bundle),
}
}
@ -73,15 +68,15 @@ impl Reconcilable for VText {
_root: &BSubtree,
_parent_scope: &AnyScope,
_parent: &Element,
_next_sibling: NodeRef,
_slot: DomSlot,
btext: &mut Self::Bundle,
) -> NodeRef {
) -> DomSlot {
let Self { text } = self;
let ancestor_text = std::mem::replace(&mut btext.text, text);
if btext.text != ancestor_text {
btext.text_node.set_node_value(Some(&btext.text));
}
NodeRef::new(btext.text_node.clone().into())
DomSlot::at(btext.text_node.clone().into())
}
}
@ -102,52 +97,47 @@ mod feat_hydration {
impl Hydratable for VText {
fn hydrate(
self,
root: &BSubtree,
parent_scope: &AnyScope,
_root: &BSubtree,
_parent_scope: &AnyScope,
parent: &Element,
fragment: &mut Fragment,
) -> (NodeRef, Self::Bundle) {
if let Some(m) = fragment.front().cloned() {
) -> Self::Bundle {
let next_sibling = if let Some(m) = fragment.front().cloned() {
// better safe than sorry.
if m.node_type() == Node::TEXT_NODE {
if let Ok(m) = m.dyn_into::<TextNode>() {
// pop current node.
fragment.pop_front();
let m = m.unchecked_into::<TextNode>();
// pop current node.
fragment.pop_front();
// TODO: It may make sense to assert the text content in the text node
// against the VText when #[cfg(debug_assertions)]
// is true, but this may be complicated.
// We always replace the text value for now.
//
// Please see the next comment for a detailed explanation.
m.set_node_value(Some(self.text.as_ref()));
// TODO: It may make sense to assert the text content in the text node
// against the VText when #[cfg(debug_assertions)]
// is true, but this may be complicated.
// We always replace the text value for now.
//
// Please see the next comment for a detailed explanation.
m.set_node_value(Some(self.text.as_ref()));
return (
NodeRef::new(m.clone().into()),
BText {
text: self.text,
text_node: m,
},
);
}
return BText {
text: self.text,
text_node: m,
};
}
}
Some(m)
} else {
fragment.sibling_at_end().cloned()
};
// If there are multiple text nodes placed back-to-back in SSR, it may be parsed as a
// single text node by browser, hence we need to add extra text nodes here
// if the next node is not a text node. Similarly, the value of the text
// node may be a combination of multiple VText vnodes. So we always need to
// override their values.
self.attach(
root,
parent_scope,
parent,
fragment
.front()
.cloned()
.map(NodeRef::new)
.unwrap_or_default(),
)
let text_node = document().create_text_node("");
DomSlot::create(next_sibling).insert(parent, &text_node);
BText {
text: "".into(),
text_node,
}
}
}
}

View File

@ -1,15 +1,15 @@
use std::collections::VecDeque;
use std::ops::{Deref, DerefMut};
use wasm_bindgen::JsCast;
use web_sys::{Element, Node};
use crate::dom_bundle::BSubtree;
use crate::html::NodeRef;
use super::{BSubtree, DomSlot};
use crate::virtual_dom::Collectable;
/// A Hydration Fragment
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct Fragment(VecDeque<Node>);
pub(crate) struct Fragment(VecDeque<Node>, Option<Node>);
impl Deref for Fragment {
type Target = VecDeque<Node>;
@ -39,7 +39,7 @@ impl Fragment {
fragment.push_back(m);
}
Self(fragment)
Self(fragment, None)
}
/// Collects nodes for a Component Bundle or a BSuspense.
@ -63,7 +63,7 @@ impl Fragment {
};
// We trim all leading text nodes as it's likely these are whitespaces.
collect_from.trim_start_text_nodes(parent);
collect_from.trim_start_text_nodes();
let first_node = collect_from
.pop_front()
@ -115,19 +115,20 @@ impl Fragment {
}
}
nodes.push_back(current_node.clone());
nodes.push_back(current_node);
}
Self(nodes)
let next_child = collect_from.0.front().cloned();
Self(nodes, next_child)
}
/// Remove child nodes until first non-text node.
pub fn trim_start_text_nodes(&mut self, parent: &Element) {
pub fn trim_start_text_nodes(&mut self) {
while let Some(ref m) = self.front().cloned() {
if m.node_type() == Node::TEXT_NODE {
self.pop_front();
parent.remove_child(m).unwrap();
m.unchecked_ref::<web_sys::Text>().remove();
} else {
break;
}
@ -141,7 +142,8 @@ impl Fragment {
.map(|m| m.clone_node_with_deep(true).expect("failed to clone node."))
.collect::<VecDeque<_>>();
Self(nodes)
// the cloned nodes are disconnected from the real dom, so next_child is `None`
Self(nodes, None)
}
// detaches current fragment.
@ -156,16 +158,16 @@ impl Fragment {
}
/// Shift current Fragment into a different position in the dom.
pub fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
pub fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
for node in self.iter() {
next_parent
.insert_before(node, next_sibling.get().as_ref())
.unwrap();
slot.insert(next_parent, node);
}
self.front()
.cloned()
.map(NodeRef::new)
.unwrap_or(next_sibling)
self.front().cloned().map(DomSlot::at).unwrap_or(slot)
}
/// Return the node that comes after all the nodes in this fragment
pub fn sibling_at_end(&self) -> Option<&Node> {
self.1.as_ref()
}
}

View File

@ -7,7 +7,7 @@
use web_sys::Element;
use crate::html::{AnyScope, NodeRef};
use crate::html::AnyScope;
use crate::virtual_dom::VNode;
mod bcomp;
@ -18,6 +18,7 @@ mod braw;
mod bsuspense;
mod btag;
mod btext;
mod position;
mod subtree_root;
mod traits;
@ -31,10 +32,11 @@ use braw::BRaw;
use bsuspense::BSuspense;
use btag::{BTag, Registry};
use btext::BText;
pub(crate) use position::{DomSlot, DynamicDomSlot};
use subtree_root::EventDescriptor;
pub use subtree_root::{set_event_bubbling, BSubtree};
use traits::{Reconcilable, ReconcileTarget};
use utils::{insert_node, test_log};
use utils::test_log;
/// A Bundle.
///
@ -53,8 +55,8 @@ impl Bundle {
}
/// Shifts the bundle into a different position.
pub fn shift(&self, next_parent: &Element, next_sibling: NodeRef) {
self.0.shift(next_parent, next_sibling);
pub fn shift(&self, next_parent: &Element, slot: DomSlot) {
self.0.shift(next_parent, slot);
}
/// Applies a virtual dom layout to current bundle.
@ -63,10 +65,10 @@ impl Bundle {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
next_node: VNode,
) -> NodeRef {
next_node.reconcile_node(root, parent_scope, parent, next_sibling, &mut self.0)
) -> DomSlot {
next_node.reconcile_node(root, parent_scope, parent, slot, &mut self.0)
}
/// Detaches current bundle.
@ -82,7 +84,7 @@ mod feat_hydration {
pub(super) use super::utils::node_type_str;
#[path = "./fragment.rs"]
mod fragment;
pub use fragment::Fragment;
pub(crate) use fragment::Fragment;
use super::*;
impl Bundle {
@ -93,9 +95,9 @@ mod feat_hydration {
parent: &Element,
fragment: &mut Fragment,
node: VNode,
) -> (NodeRef, Self) {
let (node_ref, bundle) = node.hydrate(root, parent_scope, parent, fragment);
(node_ref, Self(bundle))
) -> Self {
let bundle = node.hydrate(root, parent_scope, parent, fragment);
Self(bundle)
}
}
}

View File

@ -0,0 +1,256 @@
//! Structs for keeping track where in the DOM a node belongs
use std::cell::RefCell;
use std::rc::Rc;
use web_sys::{Element, Node};
/// A position in the list of children of an implicit parent [`Element`].
///
/// This can either be in front of a `DomSlot::at(next_sibling)`, at the end of the list with
/// `DomSlot::at_end()`, or a dynamic position in the list with [`DynamicDomSlot::to_position`].
#[derive(Clone)]
pub(crate) struct DomSlot {
variant: DomSlotVariant,
}
#[derive(Clone)]
enum DomSlotVariant {
Node(Option<Node>),
Chained(DynamicDomSlot),
}
/// A dynamic dom slot can be reassigned. This change is also seen by the [`DomSlot`] from
/// [`Self::to_position`] before the reassignment took place.
#[derive(Clone)]
pub(crate) struct DynamicDomSlot {
target: Rc<RefCell<DomSlot>>,
}
impl std::fmt::Debug for DomSlot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.with_next_sibling(|n| {
write!(
f,
"DomSlot {{ next_sibling: {:?} }}",
n.map(crate::utils::print_node)
)
})
}
}
impl std::fmt::Debug for DynamicDomSlot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:#?}", *self.target.borrow())
}
}
#[cfg(debug_assertions)]
thread_local! {
// A special marker element that should not be referenced
static TRAP: Node = gloo::utils::document().create_element("div").unwrap().into();
}
impl DomSlot {
/// Denotes the position just before the given node in its parent's list of children.
pub fn at(next_sibling: Node) -> Self {
Self::create(Some(next_sibling))
}
/// Denotes the position at the end of a list of children. The parent is implicit.
pub fn at_end() -> Self {
Self::create(None)
}
pub fn create(next_sibling: Option<Node>) -> Self {
Self {
variant: DomSlotVariant::Node(next_sibling),
}
}
/// A new "placeholder" [DomSlot] that should not be used to insert nodes
#[inline]
pub fn new_debug_trapped() -> Self {
#[cfg(debug_assertions)]
{
Self::at(TRAP.with(|trap| trap.clone()))
}
#[cfg(not(debug_assertions))]
{
Self::at_end()
}
}
/// Get the [Node] that comes just after the position, or `None` if this denotes the position at
/// the end
fn with_next_sibling<R>(&self, f: impl FnOnce(Option<&Node>) -> R) -> R {
let checkedf = |node: Option<&Node>| {
#[cfg(debug_assertions)]
TRAP.with(|trap| {
assert!(
node != Some(trap),
"Should not use a trapped DomSlot. Please report this as an internal bug in \
yew."
)
});
f(node)
};
match &self.variant {
DomSlotVariant::Node(ref n) => checkedf(n.as_ref()),
DomSlotVariant::Chained(ref chain) => chain.with_next_sibling(checkedf),
}
}
/// Insert a [Node] at the position denoted by this slot. `parent` must be the actual parent
/// element of the children that this slot is implicitly a part of.
pub(super) fn insert(&self, parent: &Element, node: &Node) {
self.with_next_sibling(|next_sibling| {
parent
.insert_before(node, next_sibling)
.unwrap_or_else(|err| {
let msg = if next_sibling.is_some() {
"failed to insert node before next sibling"
} else {
"failed to append child"
};
// Log normally, so we can inspect the nodes in console
gloo::console::error!(msg, err, parent, next_sibling, node);
// Log via tracing for consistency
tracing::error!(msg);
// Panic to short-curcuit and fail
panic!("{}", msg)
});
});
}
#[cfg(target_arch = "wasm32")]
#[cfg(test)]
fn get(&self) -> Option<Node> {
self.with_next_sibling(|n| n.cloned())
}
}
impl DynamicDomSlot {
/// Create a dynamic dom slot that initially represents ("targets") the same slot as the
/// argument.
pub fn new(initial_position: DomSlot) -> Self {
Self {
target: Rc::new(RefCell::new(initial_position)),
}
}
pub fn new_debug_trapped() -> Self {
Self::new(DomSlot::new_debug_trapped())
}
/// Change the [`DomSlot`] that is targeted. Subsequently, this will behave as if `self` was
/// created from the passed DomSlot in the first place.
pub fn reassign(&self, next_position: DomSlot) {
// TODO: is not defensive against accidental reference loops
*self.target.borrow_mut() = next_position;
}
/// Get a [`DomSlot`] that gets automatically updated when `self` gets reassigned. All such
/// slots are equivalent to each other and point to the same position.
pub fn to_position(&self) -> DomSlot {
DomSlot {
variant: DomSlotVariant::Chained(self.clone()),
}
}
fn with_next_sibling<R>(&self, f: impl FnOnce(Option<&Node>) -> R) -> R {
// we use an iterative approach to traverse a possible long chain for references
// see for example issue #3043 why a recursive call is impossible for large lists in vdom
// TODO: there could be some data structure that performs better here. E.g. a balanced tree
// with parent pointers come to mind, but they are a bit fiddly to implement in rust
let mut this = self.target.clone();
loop {
// v------- borrow lives for this match expression
let next_this = match &this.borrow().variant {
DomSlotVariant::Node(ref n) => break f(n.as_ref()),
// We clone an Rc here temporarily, so that we don't have to consume stack
// space. The alternative would be to keep the
// `Ref<'_, DomSlot>` above in some temporary buffer
DomSlotVariant::Chained(ref chain) => chain.target.clone(),
};
this = next_this;
}
}
}
#[cfg(target_arch = "wasm32")]
#[cfg(test)]
mod layout_tests {
use gloo::utils::document;
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
use super::*;
wasm_bindgen_test_configure!(run_in_browser);
#[test]
fn new_at_and_get() {
let node = document().create_element("p").unwrap();
let position = DomSlot::at(node.clone().into());
assert_eq!(
position.get().unwrap(),
node.clone().into(),
"expected the DomSlot to be at {node:#?}"
);
}
#[test]
fn new_at_end_and_get() {
let position = DomSlot::at_end();
assert!(
position.get().is_none(),
"expected the DomSlot to not have a next sibling"
);
}
#[test]
fn get_through_dynamic() {
let original = DomSlot::at(document().create_element("p").unwrap().into());
let target = DynamicDomSlot::new(original.clone());
assert_eq!(
target.to_position().get(),
original.get(),
"expected {target:#?} to point to the same position as {original:#?}"
);
}
#[test]
fn get_after_reassign() {
let target = DynamicDomSlot::new(DomSlot::at_end());
let target_pos = target.to_position();
// We reassign *after* we called `to_position` here to be strict in the test
let replacement = DomSlot::at(document().create_element("p").unwrap().into());
target.reassign(replacement.clone());
assert_eq!(
target_pos.get(),
replacement.get(),
"expected {target:#?} to point to the same position as {replacement:#?}"
);
}
#[test]
fn get_chain_after_reassign() {
let middleman = DynamicDomSlot::new(DomSlot::at_end());
let target = DynamicDomSlot::new(middleman.to_position());
let target_pos = target.to_position();
assert!(
target.to_position().get().is_none(),
"should not yet point to a node"
);
// Now reassign the middle man, but get the node from `target`
let replacement = DomSlot::at(document().create_element("p").unwrap().into());
middleman.reassign(replacement.clone());
assert_eq!(
target_pos.get(),
replacement.get(),
"expected {target:#?} to point to the same position as {replacement:#?}"
);
}
}

View File

@ -1,7 +1,7 @@
use web_sys::Element;
use super::{BNode, BSubtree};
use crate::html::{AnyScope, NodeRef};
use super::{BNode, BSubtree, DomSlot};
use crate::html::AnyScope;
/// A Reconcile Target.
///
@ -16,7 +16,7 @@ pub(super) trait ReconcileTarget {
/// Move elements from one parent to another parent.
/// This is for example used by `VSuspense` to preserve component state without detaching
/// (which destroys component state).
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef;
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot;
}
/// This trait provides features to update a tree by calculating a difference against another tree.
@ -29,19 +29,19 @@ pub(super) trait Reconcilable {
/// - `root`: bundle of the subtree root
/// - `parent_scope`: the parent `Scope` used for passing messages to the parent `Component`.
/// - `parent`: the parent node in the DOM.
/// - `next_sibling`: to find where to put the node.
/// - `slot`: to find where to put the node.
///
/// Returns a reference to the newly inserted element.
/// The [`NodeRef`] points the first element (if there are multiple nodes created),
/// or is the passed in next_sibling if there are no element is created.
/// The [`DomSlot`] points the first element (if there are multiple nodes created),
/// or is the passed in `slot` if there are no element is created.
fn attach(
self,
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
) -> (NodeRef, Self::Bundle);
slot: DomSlot,
) -> (DomSlot, Self::Bundle);
/// Scoped diff apply to other tree.
///
@ -52,7 +52,7 @@ pub(super) trait Reconcilable {
/// Parameters:
/// - `parent_scope`: the parent `Scope` used for passing messages to the parent `Component`.
/// - `parent`: the parent node in the DOM.
/// - `next_sibling`: the next sibling, used to efficiently find where to put the node.
/// - `slot`: the slot in `parent`'s children where to put the node.
/// - `bundle`: the node that this node will be replacing in the DOM. This method will remove
/// the `bundle` from the `parent` if it is of the wrong kind, and otherwise reuse it.
///
@ -63,18 +63,18 @@ pub(super) trait Reconcilable {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
bundle: &mut BNode,
) -> NodeRef;
) -> DomSlot;
fn reconcile(
self,
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
bundle: &mut Self::Bundle,
) -> NodeRef;
) -> DomSlot;
/// Replace an existing bundle by attaching self and detaching the existing one
fn replace(
@ -83,14 +83,14 @@ pub(super) trait Reconcilable {
root: &BSubtree,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
slot: DomSlot,
bundle: &mut BNode,
) -> NodeRef
) -> DomSlot
where
Self: Sized,
Self::Bundle: Into<BNode>,
{
let (self_ref, self_) = self.attach(root, parent_scope, parent, next_sibling);
let (self_ref, self_) = self.attach(root, parent_scope, parent, slot);
let ancestor = std::mem::replace(bundle, self_.into());
ancestor.detach(root, parent, false);
self_ref
@ -116,7 +116,7 @@ mod feat_hydration {
parent_scope: &AnyScope,
parent: &Element,
fragment: &mut Fragment,
) -> (NodeRef, Self::Bundle);
) -> Self::Bundle;
}
}

View File

@ -1,28 +1,3 @@
use web_sys::{Element, Node};
/// Insert a concrete [Node] into the DOM
pub(super) fn insert_node(node: &Node, parent: &Element, next_sibling: Option<&Node>) {
match next_sibling {
Some(next_sibling) => parent
.insert_before(node, Some(next_sibling))
.unwrap_or_else(|err| {
// Log normally, so we can inspect the nodes in console
gloo::console::error!(
"failed to insert node before next sibling",
err,
parent,
next_sibling,
node
);
// Log via tracing for consistency
tracing::error!("failed to insert node before next sibling");
// Panic to short-curcuit and fail
panic!("failed to insert node before next sibling")
}),
None => parent.append_child(node).expect("failed to append child"),
};
}
#[cfg(all(test, target_arch = "wasm32", verbose_tests))]
macro_rules! test_log {
($fmt:literal, $($arg:expr),* $(,)?) => {
@ -45,9 +20,7 @@ mod feat_hydration {
use std::borrow::Cow;
use wasm_bindgen::JsCast;
use web_sys::Element;
use super::*;
use web_sys::{Element, Node};
pub(in crate::dom_bundle) fn node_type_str(node: &Node) -> Cow<'static, str> {
match node.node_type() {
@ -85,9 +58,8 @@ mod tests {
use gloo::utils::document;
use web_sys::Element;
use crate::dom_bundle::BSubtree;
use crate::dom_bundle::{BSubtree, DomSlot};
use crate::html::AnyScope;
use crate::NodeRef;
pub fn setup_parent() -> (BSubtree, AnyScope, Element) {
let scope = AnyScope::test();
@ -101,7 +73,7 @@ mod tests {
pub const SIBLING_CONTENT: &str = "END";
pub fn setup_parent_and_sibling() -> (BSubtree, AnyScope, Element, NodeRef) {
pub(crate) fn setup_parent_and_sibling() -> (BSubtree, AnyScope, Element, DomSlot) {
let scope = AnyScope::test();
let parent = document().create_element("div").unwrap();
let root = BSubtree::create_root(&parent);
@ -110,7 +82,7 @@ mod tests {
let end = document().create_text_node(SIBLING_CONTENT);
parent.append_child(&end).unwrap();
let sibling = NodeRef::new(end.into());
let sibling = DomSlot::at(end.into());
(root, scope, parent, sibling)
}

View File

@ -11,9 +11,7 @@ use super::BaseComponent;
#[cfg(feature = "hydration")]
use crate::dom_bundle::Fragment;
#[cfg(feature = "csr")]
use crate::dom_bundle::{BSubtree, Bundle};
#[cfg(feature = "csr")]
use crate::html::NodeRef;
use crate::dom_bundle::{BSubtree, Bundle, DomSlot, DynamicDomSlot};
#[cfg(feature = "hydration")]
use crate::html::RenderMode;
use crate::html::{Html, RenderError};
@ -27,16 +25,19 @@ pub(crate) enum ComponentRenderState {
bundle: Bundle,
root: BSubtree,
parent: Element,
next_sibling: NodeRef,
internal_ref: NodeRef,
/// The dom position in front of the next sibling
sibling_slot: DynamicDomSlot,
/// The dom position in front of this component. Adjusted whenever this component
/// re-renders.
own_slot: DynamicDomSlot,
},
#[cfg(feature = "hydration")]
Hydration {
fragment: Fragment,
root: BSubtree,
parent: Element,
next_sibling: NodeRef,
internal_ref: NodeRef,
sibling_slot: DynamicDomSlot,
own_slot: DynamicDomSlot,
},
#[cfg(feature = "ssr")]
Ssr {
@ -52,31 +53,31 @@ impl std::fmt::Debug for ComponentRenderState {
ref bundle,
root,
ref parent,
ref next_sibling,
ref internal_ref,
ref sibling_slot,
ref own_slot,
} => f
.debug_struct("ComponentRenderState::Render")
.field("bundle", bundle)
.field("root", root)
.field("parent", parent)
.field("next_sibling", next_sibling)
.field("internal_ref", internal_ref)
.field("sibling_slot", sibling_slot)
.field("own_slot", own_slot)
.finish(),
#[cfg(feature = "hydration")]
Self::Hydration {
ref fragment,
ref parent,
ref next_sibling,
ref internal_ref,
ref sibling_slot,
ref own_slot,
ref root,
} => f
.debug_struct("ComponentRenderState::Hydration")
.field("fragment", fragment)
.field("root", root)
.field("parent", parent)
.field("next_sibling", next_sibling)
.field("internal_ref", internal_ref)
.field("sibling_slot", sibling_slot)
.field("own_slot", own_slot)
.finish(),
#[cfg(feature = "ssr")]
@ -96,31 +97,31 @@ impl std::fmt::Debug for ComponentRenderState {
#[cfg(feature = "csr")]
impl ComponentRenderState {
pub(crate) fn shift(&mut self, next_parent: Element, next_next_sibling: NodeRef) {
pub(crate) fn shift(&mut self, next_parent: Element, next_slot: DomSlot) {
match self {
#[cfg(feature = "csr")]
Self::Render {
bundle,
parent,
next_sibling,
sibling_slot,
..
} => {
bundle.shift(&next_parent, next_next_sibling.clone());
bundle.shift(&next_parent, next_slot.clone());
*parent = next_parent;
next_sibling.link(next_next_sibling);
sibling_slot.reassign(next_slot);
}
#[cfg(feature = "hydration")]
Self::Hydration {
fragment,
parent,
next_sibling,
sibling_slot,
..
} => {
fragment.shift(&next_parent, next_next_sibling.clone());
fragment.shift(&next_parent, next_slot.clone());
*parent = next_parent;
next_sibling.link(next_next_sibling);
sibling_slot.reassign(next_slot);
}
#[cfg(feature = "ssr")]
@ -386,13 +387,10 @@ impl ComponentState {
ComponentRenderState::Render {
bundle,
ref parent,
ref internal_ref,
ref root,
..
} => {
bundle.detach(root, parent, parent_to_detach);
internal_ref.set(None);
}
// We need to detach the hydrate fragment if the component is not hydrated.
#[cfg(feature = "hydration")]
@ -400,12 +398,9 @@ impl ComponentState {
ref root,
fragment,
ref parent,
ref internal_ref,
..
} => {
fragment.detach(root, parent, parent_to_detach);
internal_ref.set(None);
}
#[cfg(feature = "ssr")]
@ -496,18 +491,15 @@ impl ComponentState {
ref mut bundle,
ref parent,
ref root,
ref next_sibling,
ref internal_ref,
ref sibling_slot,
ref mut own_slot,
..
} => {
let scope = self.inner.any_scope();
#[cfg(feature = "hydration")]
next_sibling.debug_assert_not_trapped();
let new_node_ref =
bundle.reconcile(root, &scope, parent, next_sibling.clone(), new_root);
internal_ref.link(new_node_ref);
bundle.reconcile(root, &scope, parent, sibling_slot.to_position(), new_root);
own_slot.reassign(new_node_ref);
let first_render = !self.has_rendered;
self.has_rendered = true;
@ -526,12 +518,12 @@ impl ComponentState {
ComponentRenderState::Hydration {
ref mut fragment,
ref parent,
ref internal_ref,
ref next_sibling,
ref mut own_slot,
ref mut sibling_slot,
ref root,
} => {
// We schedule a "first" render to run immediately after hydration,
// to fix NodeRefs (first_node and next_sibling).
// to fix NodeRefs (first_node and slot).
scheduler::push_component_priority_render(
self.comp_id,
Box::new(RenderRunner {
@ -544,21 +536,22 @@ impl ComponentState {
// This first node is not guaranteed to be correct here.
// As it may be a comment node that is removed afterwards.
// but we link it anyways.
let (node, bundle) = Bundle::hydrate(root, &scope, parent, fragment, new_root);
let bundle = Bundle::hydrate(root, &scope, parent, fragment, new_root);
// We trim all text nodes before checking as it's likely these are whitespaces.
fragment.trim_start_text_nodes(parent);
fragment.trim_start_text_nodes();
assert!(fragment.is_empty(), "expected end of component, found node");
internal_ref.link(node);
self.render_state = ComponentRenderState::Render {
root: root.clone(),
bundle,
parent: parent.clone(),
internal_ref: internal_ref.clone(),
next_sibling: next_sibling.clone(),
own_slot: std::mem::replace(own_slot, DynamicDomSlot::new_debug_trapped()),
sibling_slot: std::mem::replace(
sibling_slot,
DynamicDomSlot::new_debug_trapped(),
),
};
}
@ -592,7 +585,7 @@ mod feat_csr {
pub(crate) struct PropsUpdateRunner {
pub state: Shared<Option<ComponentState>>,
pub props: Option<Rc<dyn Any>>,
pub next_sibling: Option<NodeRef>,
pub next_sibling_slot: Option<DomSlot>,
}
impl ComponentState {
@ -601,26 +594,24 @@ mod feat_csr {
skip(self),
fields(component.id = self.comp_id)
)]
fn changed(&mut self, props: Option<Rc<dyn Any>>, next_sibling: Option<NodeRef>) -> bool {
if let Some(next_sibling) = next_sibling {
fn changed(
&mut self,
props: Option<Rc<dyn Any>>,
next_sibling_slot: Option<DomSlot>,
) -> bool {
if let Some(next_sibling_slot) = next_sibling_slot {
// When components are updated, their siblings were likely also updated
// We also need to shift the bundle so next sibling will be synced to child
// components.
match self.render_state {
match &mut self.render_state {
#[cfg(feature = "csr")]
ComponentRenderState::Render {
next_sibling: ref current_next_sibling,
..
} => {
current_next_sibling.link(next_sibling);
ComponentRenderState::Render { sibling_slot, .. } => {
sibling_slot.reassign(next_sibling_slot);
}
#[cfg(feature = "hydration")]
ComponentRenderState::Hydration {
next_sibling: ref current_next_sibling,
..
} => {
current_next_sibling.link(next_sibling);
ComponentRenderState::Hydration { sibling_slot, .. } => {
sibling_slot.reassign(next_sibling_slot);
}
#[cfg(feature = "ssr")]
@ -681,13 +672,13 @@ mod feat_csr {
impl Runnable for PropsUpdateRunner {
fn run(self: Box<Self>) {
let Self {
next_sibling,
next_sibling_slot,
props,
state: shared_state,
} = *self;
if let Some(state) = shared_state.borrow_mut().as_mut() {
let schedule_render = state.changed(props, next_sibling);
let schedule_render = state.changed(props, next_sibling_slot);
if schedule_render {
scheduler::push_component_render(
@ -738,7 +729,7 @@ mod feat_csr {
scheduler::push_component_props_update(Box::new(PropsUpdateRunner {
state: self.state.clone(),
props: None,
next_sibling: None,
next_sibling_slot: None,
}));
}
}
@ -886,8 +877,8 @@ mod tests {
scope.mount_in_place(
root,
parent,
NodeRef::default(),
NodeRef::default(),
DomSlot::at_end(),
DynamicDomSlot::new_debug_trapped(),
Rc::new(props),
);
crate::scheduler::start_now();

View File

@ -520,11 +520,10 @@ mod feat_csr {
use web_sys::Element;
use super::*;
use crate::dom_bundle::{BSubtree, Bundle};
use crate::dom_bundle::{BSubtree, Bundle, DomSlot, DynamicDomSlot};
use crate::html::component::lifecycle::{
ComponentRenderState, CreateRunner, DestroyRunner, PropsUpdateRunner, RenderRunner,
};
use crate::html::NodeRef;
use crate::scheduler;
impl AnyScope {
@ -541,11 +540,11 @@ mod feat_csr {
fn schedule_props_update(
state: Shared<Option<ComponentState>>,
props: Rc<dyn Any>,
next_sibling: NodeRef,
next_sibling_slot: DomSlot,
) {
scheduler::push_component_props_update(Box::new(PropsUpdateRunner {
state,
next_sibling: Some(next_sibling),
next_sibling_slot: Some(next_sibling_slot),
props: Some(props),
}));
// Not guaranteed to already have the scheduler started
@ -561,20 +560,20 @@ mod feat_csr {
&self,
root: BSubtree,
parent: Element,
next_sibling: NodeRef,
internal_ref: NodeRef,
slot: DomSlot,
internal_ref: DynamicDomSlot,
props: Rc<COMP::Properties>,
) {
let bundle = Bundle::new();
internal_ref.link(next_sibling.clone());
let stable_next_sibling = NodeRef::default();
stable_next_sibling.link(next_sibling);
let sibling_slot = DynamicDomSlot::new(slot);
internal_ref.reassign(sibling_slot.to_position());
let state = ComponentRenderState::Render {
bundle,
root,
internal_ref,
own_slot: internal_ref,
parent,
next_sibling: stable_next_sibling,
sibling_slot,
};
scheduler::push_component_create(
@ -594,8 +593,8 @@ mod feat_csr {
scheduler::start();
}
pub(crate) fn reuse(&self, props: Rc<COMP::Properties>, next_sibling: NodeRef) {
schedule_props_update(self.state.clone(), props, next_sibling)
pub(crate) fn reuse(&self, props: Rc<COMP::Properties>, slot: DomSlot) {
schedule_props_update(self.state.clone(), props, slot)
}
}
@ -604,7 +603,7 @@ mod feat_csr {
/// Get the render state if it hasn't already been destroyed
fn render_state(&self) -> Option<Ref<'_, ComponentRenderState>>;
/// Shift the node associated with this scope to a new place
fn shift_node(&self, parent: Element, next_sibling: NodeRef);
fn shift_node(&self, parent: Element, slot: DomSlot);
/// Process an event to destroy a component
fn destroy(self, parent_to_detach: bool);
fn destroy_boxed(self: Box<Self>, parent_to_detach: bool);
@ -640,10 +639,10 @@ mod feat_csr {
self.destroy(parent_to_detach)
}
fn shift_node(&self, parent: Element, next_sibling: NodeRef) {
fn shift_node(&self, parent: Element, slot: DomSlot) {
let mut state_ref = self.state.borrow_mut();
if let Some(render_state) = state_ref.as_mut() {
render_state.render_state.shift(parent, next_sibling)
render_state.render_state.shift(parent, slot)
}
}
}
@ -657,9 +656,8 @@ mod feat_hydration {
use web_sys::{Element, HtmlScriptElement};
use super::*;
use crate::dom_bundle::{BSubtree, Fragment};
use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot, Fragment};
use crate::html::component::lifecycle::{ComponentRenderState, CreateRunner, RenderRunner};
use crate::html::NodeRef;
use crate::scheduler;
use crate::virtual_dom::Collectable;
@ -680,7 +678,7 @@ mod feat_hydration {
root: BSubtree,
parent: Element,
fragment: &mut Fragment,
internal_ref: NodeRef,
internal_ref: DynamicDomSlot,
props: Rc<COMP::Properties>,
) {
// This is very helpful to see which component is failing during hydration
@ -695,14 +693,12 @@ mod feat_hydration {
let collectable = Collectable::for_component::<COMP>();
let mut fragment = Fragment::collect_between(fragment, &collectable, &parent);
match fragment.front().cloned() {
front @ Some(_) => internal_ref.set(front),
None =>
{
#[cfg(debug_assertions)]
internal_ref.link(NodeRef::new_debug_trapped())
}
}
let next_sibling = if let Some(n) = fragment.front() {
Some(n.clone())
} else {
fragment.sibling_at_end().cloned()
};
internal_ref.reassign(DomSlot::create(next_sibling));
let prepared_state = match fragment
.back()
@ -720,8 +716,8 @@ mod feat_hydration {
let state = ComponentRenderState::Hydration {
parent,
root,
internal_ref,
next_sibling: NodeRef::new_debug_trapped(),
own_slot: internal_ref,
sibling_slot: DynamicDomSlot::new_debug_trapped(),
fragment,
};

View File

@ -92,7 +92,7 @@ pub struct NodeRef(Rc<RefCell<NodeRefInner>>);
impl PartialEq for NodeRef {
fn eq(&self, other: &Self) -> bool {
self.0.as_ptr() == other.0.as_ptr() || Some(self) == other.0.borrow().link.as_ref()
self.0.as_ptr() == other.0.as_ptr()
}
}
@ -109,14 +109,13 @@ impl std::fmt::Debug for NodeRef {
#[derive(PartialEq, Debug, Default, Clone)]
struct NodeRefInner {
node: Option<Node>,
link: Option<NodeRef>,
}
impl NodeRef {
/// Get the wrapped Node reference if it exists
pub fn get(&self) -> Option<Node> {
let inner = self.0.borrow();
inner.node.clone().or_else(|| inner.link.as_ref()?.get())
inner.node.clone()
}
/// Try converting the node reference into another form
@ -131,68 +130,9 @@ mod feat_csr {
use super::*;
impl NodeRef {
/// Link a downstream `NodeRef`
pub(crate) fn link(&self, node_ref: Self) {
// Avoid circular references
debug_assert!(
self != &node_ref,
"no circular references allowed! Report this as a bug in yew"
);
let mut this = self.0.borrow_mut();
this.node = None;
this.link = Some(node_ref);
}
/// Wrap an existing `Node` in a `NodeRef`
pub(crate) fn new(node: Node) -> Self {
let node_ref = NodeRef::default();
node_ref.set(Some(node));
node_ref
}
/// Place a Node in a reference for later use
pub(crate) fn set(&self, node: Option<Node>) {
let mut this = self.0.borrow_mut();
this.node = node;
this.link = None;
}
}
}
#[cfg(feature = "hydration")]
mod feat_hydration {
use super::*;
#[cfg(debug_assertions)]
thread_local! {
// A special marker element that should not be referenced
static TRAP: Node = gloo::utils::document().create_element("div").unwrap().into();
}
impl NodeRef {
// A new "placeholder" node ref that should not be accessed
#[inline]
pub(crate) fn new_debug_trapped() -> Self {
#[cfg(debug_assertions)]
{
Self::new(TRAP.with(|trap| trap.clone()))
}
#[cfg(not(debug_assertions))]
{
Self::default()
}
}
#[inline]
pub(crate) fn debug_assert_not_trapped(&self) {
#[cfg(debug_assertions)]
TRAP.with(|trap| {
assert!(
self.get().as_ref() != Some(trap),
"should not use a trapped node ref"
)
})
pub(crate) fn set(&self, new_ref: Option<Node>) {
let mut inner = self.0.borrow_mut();
inner.node = new_ref;
}
}
}

View File

@ -2,9 +2,8 @@
//!
//! This tests must be run in browser and thus require the `csr` feature to be enabled
use gloo::console::log;
use yew::NodeRef;
use crate::dom_bundle::{BSubtree, Bundle};
use crate::dom_bundle::{BSubtree, Bundle, DomSlot};
use crate::html::AnyScope;
use crate::virtual_dom::VNode;
use crate::{scheduler, Component, Context, Html};
@ -48,14 +47,14 @@ pub fn diff_layouts(layouts: Vec<TestLayout<'_>>) {
parent_element.append_child(&end_node).unwrap();
// Tests each layout independently
let next_sibling = NodeRef::new(end_node.into());
let slot = DomSlot::at(end_node.into());
for layout in layouts.iter() {
// Apply the layout
let vnode = layout.node.clone();
log!("Independently apply layout '{}'", layout.name);
let mut bundle = Bundle::new();
bundle.reconcile(&root, &scope, &parent_element, next_sibling.clone(), vnode);
bundle.reconcile(&root, &scope, &parent_element, slot.clone(), vnode);
scheduler::start_now();
assert_eq!(
parent_element.inner_html(),
@ -69,7 +68,7 @@ pub fn diff_layouts(layouts: Vec<TestLayout<'_>>) {
log!("Independently reapply layout '{}'", layout.name);
bundle.reconcile(&root, &scope, &parent_element, next_sibling.clone(), vnode);
bundle.reconcile(&root, &scope, &parent_element, slot.clone(), vnode);
scheduler::start_now();
assert_eq!(
parent_element.inner_html(),
@ -95,13 +94,7 @@ pub fn diff_layouts(layouts: Vec<TestLayout<'_>>) {
let next_vnode = layout.node.clone();
log!("Sequentially apply layout '{}'", layout.name);
bundle.reconcile(
&root,
&scope,
&parent_element,
next_sibling.clone(),
next_vnode,
);
bundle.reconcile(&root, &scope, &parent_element, slot.clone(), next_vnode);
scheduler::start_now();
assert_eq!(
@ -117,13 +110,7 @@ pub fn diff_layouts(layouts: Vec<TestLayout<'_>>) {
let next_vnode = layout.node.clone();
log!("Sequentially detach layout '{}'", layout.name);
bundle.reconcile(
&root,
&scope,
&parent_element,
next_sibling.clone(),
next_vnode,
);
bundle.reconcile(&root, &scope, &parent_element, slot.clone(), next_vnode);
scheduler::start_now();
assert_eq!(

View File

@ -10,15 +10,15 @@ use futures::future::{FutureExt, LocalBoxFuture};
use web_sys::Element;
use super::Key;
#[cfg(feature = "csr")]
use crate::dom_bundle::BSubtree;
#[cfg(feature = "hydration")]
use crate::dom_bundle::Fragment;
#[cfg(feature = "csr")]
use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot};
use crate::html::BaseComponent;
#[cfg(feature = "csr")]
use crate::html::Scoped;
#[cfg(any(feature = "ssr", feature = "csr"))]
use crate::html::{AnyScope, Scope};
#[cfg(feature = "csr")]
use crate::html::{NodeRef, Scoped};
#[cfg(feature = "ssr")]
use crate::platform::fmt::BufWriter;
@ -61,12 +61,12 @@ pub(crate) trait Mountable {
root: &BSubtree,
parent_scope: &AnyScope,
parent: Element,
internal_ref: NodeRef,
next_sibling: NodeRef,
slot: DomSlot,
internal_ref: DynamicDomSlot,
) -> Box<dyn Scoped>;
#[cfg(feature = "csr")]
fn reuse(self: Box<Self>, scope: &dyn Scoped, next_sibling: NodeRef);
fn reuse(self: Box<Self>, scope: &dyn Scoped, slot: DomSlot);
#[cfg(feature = "ssr")]
fn render_into_stream<'a>(
@ -82,7 +82,7 @@ pub(crate) trait Mountable {
root: BSubtree,
parent_scope: &AnyScope,
parent: Element,
internal_ref: NodeRef,
internal_ref: DynamicDomSlot,
fragment: &mut Fragment,
) -> Box<dyn Scoped>;
}
@ -111,19 +111,19 @@ impl<COMP: BaseComponent> Mountable for PropsWrapper<COMP> {
root: &BSubtree,
parent_scope: &AnyScope,
parent: Element,
internal_ref: NodeRef,
next_sibling: NodeRef,
slot: DomSlot,
internal_ref: DynamicDomSlot,
) -> Box<dyn Scoped> {
let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
scope.mount_in_place(root.clone(), parent, next_sibling, internal_ref, self.props);
scope.mount_in_place(root.clone(), parent, slot, internal_ref, self.props);
Box::new(scope)
}
#[cfg(feature = "csr")]
fn reuse(self: Box<Self>, scope: &dyn Scoped, next_sibling: NodeRef) {
fn reuse(self: Box<Self>, scope: &dyn Scoped, slot: DomSlot) {
let scope: Scope<COMP> = scope.to_any().downcast::<COMP>();
scope.reuse(self.props, next_sibling);
scope.reuse(self.props, slot);
}
#[cfg(feature = "ssr")]
@ -149,7 +149,7 @@ impl<COMP: BaseComponent> Mountable for PropsWrapper<COMP> {
root: BSubtree,
parent_scope: &AnyScope,
parent: Element,
internal_ref: NodeRef,
internal_ref: DynamicDomSlot,
fragment: &mut Fragment,
) -> Box<dyn Scoped> {
let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));

View File

@ -25,8 +25,8 @@ impl VPortal {
}
/// Creates a [VPortal] rendering `content` in the DOM hierarchy under `host`.
/// If `next_sibling` is given, the content is inserted before that [Node].
/// The parent of `next_sibling`, if given, must be `host`.
/// If `inner_sibling` is given, the content is inserted before that [Node].
/// The parent of `inner_sibling`, if given, must be `host`.
pub fn new_before(content: VNode, host: Element, inner_sibling: Option<Node>) -> Self {
Self {
host,