Skip to content

Commit b7a599b

Browse files
authored
Fix internally tracked sibling positions earlier when hydrating (#3914)
* fix internally tracked sibling positions earlier previously, the fixup was done in the first "real" render, now this happens immediately in the render that transitions from ComponentRenderState::Hydration to ComponentRenderState::Render. We do so by threading through a reference to the sibling that needs a fix * test case for element with early change in render order harden test case by using an extra inner component
1 parent b246e0d commit b7a599b

File tree

15 files changed

+216
-114
lines changed

15 files changed

+216
-114
lines changed

packages/yew/src/app_handle.rs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::rc::Rc;
55

66
use web_sys::Element;
77

8-
use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot};
8+
use crate::dom_bundle::{BSubtree, DomSlot};
99
use crate::html::{BaseComponent, Scope, Scoped};
1010

1111
/// An instance of an application.
@@ -34,13 +34,9 @@ where
3434
scope: Scope::new(None),
3535
};
3636
let hosting_root = BSubtree::create_root(&host);
37-
app.scope.mount_in_place(
38-
hosting_root,
39-
host,
40-
DomSlot::at_end(),
41-
DynamicDomSlot::new_debug_trapped(),
42-
props,
43-
);
37+
let _ = app
38+
.scope
39+
.mount_in_place(hosting_root, host, DomSlot::at_end(), props);
4440

4541
app
4642
}
@@ -110,15 +106,17 @@ mod feat_hydration {
110106
let mut fragment = Fragment::collect_children(&host);
111107
let hosting_root = BSubtree::create_root(&host);
112108

109+
let mut previous_next_sibling = None;
113110
app.scope.hydrate_in_place(
114111
hosting_root,
115112
host.clone(),
116113
&mut fragment,
117-
DynamicDomSlot::new_debug_trapped(),
118114
Rc::clone(&props),
115+
&mut previous_next_sibling,
119116
);
120-
#[cfg(debug_assertions)] // Fix trapped next_sibling at the root
121-
app.scope.reuse(props, DomSlot::at_end());
117+
if let Some(previous_next_sibling) = previous_next_sibling {
118+
previous_next_sibling.reassign(DomSlot::at_end());
119+
}
122120

123121
// We remove all remaining nodes, this mimics the clear_element behaviour in
124122
// mount_with_props.

packages/yew/src/dom_bundle/bcomp.rs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,23 +63,16 @@ impl Reconcilable for VComp {
6363
key,
6464
..
6565
} = self;
66-
let internal_ref = DynamicDomSlot::new_debug_trapped();
67-
68-
let scope = mountable.mount(
69-
root,
70-
parent_scope,
71-
parent.to_owned(),
72-
slot,
73-
internal_ref.clone(),
74-
);
66+
67+
let (scope, internal_ref) = mountable.mount(root, parent_scope, parent.to_owned(), slot);
7568

7669
(
7770
internal_ref.to_position(),
7871
BComp {
7972
type_id,
73+
scope,
8074
own_position: internal_ref,
8175
key,
82-
scope,
8376
},
8477
)
8578
}
@@ -131,27 +124,27 @@ mod feat_hydration {
131124
parent_scope: &AnyScope,
132125
parent: &Element,
133126
fragment: &mut Fragment,
127+
prev_next_sibling: &mut Option<DynamicDomSlot>,
134128
) -> Self::Bundle {
135129
let VComp {
136130
type_id,
137131
mountable,
138132
key,
139133
..
140134
} = self;
141-
let internal_ref = DynamicDomSlot::new_debug_trapped();
142135

143-
let scoped = mountable.hydrate(
136+
let (scope, own_slot) = mountable.hydrate(
144137
root.clone(),
145138
parent_scope,
146139
parent.clone(),
147-
internal_ref.clone(),
148140
fragment,
141+
prev_next_sibling,
149142
);
150143

151144
BComp {
152145
type_id,
153-
scope: scoped,
154-
own_position: internal_ref,
146+
scope,
147+
own_position: own_slot,
155148
key,
156149
}
157150
}

packages/yew/src/dom_bundle/blist.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ impl Reconcilable for VList {
494494
#[cfg(feature = "hydration")]
495495
mod feat_hydration {
496496
use super::*;
497-
use crate::dom_bundle::{Fragment, Hydratable};
497+
use crate::dom_bundle::{DynamicDomSlot, Fragment, Hydratable};
498498

499499
impl Hydratable for VList {
500500
fn hydrate(
@@ -503,13 +503,14 @@ mod feat_hydration {
503503
parent_scope: &AnyScope,
504504
parent: &Element,
505505
fragment: &mut Fragment,
506+
prev_next_sibling: &mut Option<DynamicDomSlot>,
506507
) -> Self::Bundle {
507508
let (key, fully_keyed, vchildren) = self.split_for_blist();
508509

509510
let mut children = Vec::with_capacity(vchildren.len());
510511

511512
for child in vchildren.into_iter() {
512-
let child = child.hydrate(root, parent_scope, parent, fragment);
513+
let child = child.hydrate(root, parent_scope, parent, fragment, prev_next_sibling);
513514

514515
children.push(child);
515516
}

packages/yew/src/dom_bundle/bnode.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ impl fmt::Debug for BNode {
267267
#[cfg(feature = "hydration")]
268268
mod feat_hydration {
269269
use super::*;
270-
use crate::dom_bundle::{Fragment, Hydratable};
270+
use crate::dom_bundle::{DynamicDomSlot, Fragment, Hydratable};
271271

272272
impl Hydratable for VNode {
273273
fn hydrate(
@@ -276,17 +276,20 @@ mod feat_hydration {
276276
parent_scope: &AnyScope,
277277
parent: &Element,
278278
fragment: &mut Fragment,
279+
prev_next_sibling: &mut Option<DynamicDomSlot>,
279280
) -> Self::Bundle {
280281
match self {
281282
VNode::VTag(vtag) => RcExt::unwrap_or_clone(vtag)
282-
.hydrate(root, parent_scope, parent, fragment)
283+
.hydrate(root, parent_scope, parent, fragment, prev_next_sibling)
284+
.into(),
285+
VNode::VText(vtext) => vtext
286+
.hydrate(root, parent_scope, parent, fragment, prev_next_sibling)
283287
.into(),
284-
VNode::VText(vtext) => vtext.hydrate(root, parent_scope, parent, fragment).into(),
285288
VNode::VComp(vcomp) => RcExt::unwrap_or_clone(vcomp)
286-
.hydrate(root, parent_scope, parent, fragment)
289+
.hydrate(root, parent_scope, parent, fragment, prev_next_sibling)
287290
.into(),
288291
VNode::VList(vlist) => RcExt::unwrap_or_clone(vlist)
289-
.hydrate(root, parent_scope, parent, fragment)
292+
.hydrate(root, parent_scope, parent, fragment, prev_next_sibling)
290293
.into(),
291294
// You cannot hydrate a VRef.
292295
VNode::VRef(_) => {
@@ -303,9 +306,11 @@ mod feat_hydration {
303306
)
304307
}
305308
VNode::VSuspense(vsuspense) => RcExt::unwrap_or_clone(vsuspense)
306-
.hydrate(root, parent_scope, parent, fragment)
309+
.hydrate(root, parent_scope, parent, fragment, prev_next_sibling)
310+
.into(),
311+
VNode::VRaw(vraw) => vraw
312+
.hydrate(root, parent_scope, parent, fragment, prev_next_sibling)
307313
.into(),
308-
VNode::VRaw(vraw) => vraw.hydrate(root, parent_scope, parent, fragment).into(),
309314
}
310315
}
311316
}

packages/yew/src/dom_bundle/braw.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ impl Reconcilable for VRaw {
142142
#[cfg(feature = "hydration")]
143143
mod feat_hydration {
144144
use super::*;
145-
use crate::dom_bundle::{Fragment, Hydratable};
145+
use crate::dom_bundle::{DynamicDomSlot, Fragment, Hydratable};
146146
use crate::virtual_dom::Collectable;
147147

148148
impl Hydratable for VRaw {
@@ -152,15 +152,24 @@ mod feat_hydration {
152152
_parent_scope: &AnyScope,
153153
parent: &Element,
154154
fragment: &mut Fragment,
155+
prev_next_sibling: &mut Option<DynamicDomSlot>,
155156
) -> Self::Bundle {
156157
let collectable = Collectable::Raw;
157158
let fallback_fragment = Fragment::collect_between(fragment, &collectable, parent);
159+
let first_child = fallback_fragment.iter().next().cloned();
160+
161+
if let (Some(first_child), prev_next_sibling) = (&first_child, prev_next_sibling) {
162+
if let Some(prev_next_sibling) = prev_next_sibling {
163+
prev_next_sibling.reassign(DomSlot::at(first_child.clone()));
164+
}
165+
*prev_next_sibling = None;
166+
}
158167

159168
let Self { html } = self;
160169

161170
BRaw {
162171
children_count: fallback_fragment.len(),
163-
reference: fallback_fragment.iter().next().cloned(),
172+
reference: first_child,
164173
html,
165174
}
166175
}

packages/yew/src/dom_bundle/bsuspense.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ impl Reconcilable for VSuspense {
224224
#[cfg(feature = "hydration")]
225225
mod feat_hydration {
226226
use super::*;
227-
use crate::dom_bundle::{Fragment, Hydratable};
227+
use crate::dom_bundle::{DynamicDomSlot, Fragment, Hydratable};
228228
use crate::virtual_dom::Collectable;
229229

230230
impl Hydratable for VSuspense {
@@ -234,6 +234,7 @@ mod feat_hydration {
234234
parent_scope: &AnyScope,
235235
parent: &Element,
236236
fragment: &mut Fragment,
237+
previous_next_sibling: &mut Option<DynamicDomSlot>,
237238
) -> Self::Bundle {
238239
let detached_parent = document()
239240
.create_element("div")
@@ -250,9 +251,13 @@ mod feat_hydration {
250251

251252
// Even if initially suspended, these children correspond to the first non-suspended
252253
// content Refer to VSuspense::render_to_string
253-
let children_bundle =
254-
self.children
255-
.hydrate(root, parent_scope, &detached_parent, &mut nodes);
254+
let children_bundle = self.children.hydrate(
255+
root,
256+
parent_scope,
257+
&detached_parent,
258+
&mut nodes,
259+
previous_next_sibling,
260+
);
256261

257262
// We trim all leading text nodes before checking as it's likely these are whitespaces.
258263
nodes.trim_start_text_nodes();

packages/yew/src/dom_bundle/btag/mod.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ mod feat_hydration {
337337
use web_sys::Node;
338338

339339
use super::*;
340-
use crate::dom_bundle::{node_type_str, Fragment, Hydratable};
340+
use crate::dom_bundle::{node_type_str, DynamicDomSlot, Fragment, Hydratable};
341341

342342
impl Hydratable for VTag {
343343
fn hydrate(
@@ -346,6 +346,7 @@ mod feat_hydration {
346346
parent_scope: &AnyScope,
347347
_parent: &Element,
348348
fragment: &mut Fragment,
349+
prev_next_sibling: &mut Option<DynamicDomSlot>,
349350
) -> Self::Bundle {
350351
let tag_name = self.tag().to_owned();
351352

@@ -412,7 +413,12 @@ mod feat_hydration {
412413
}
413414
VTagInner::Other { children, tag } => {
414415
let mut nodes = Fragment::collect_children(&el);
415-
let child_bundle = children.hydrate(root, parent_scope, &el, &mut nodes);
416+
let mut prev_next_child = None;
417+
let child_bundle =
418+
children.hydrate(root, parent_scope, &el, &mut nodes, &mut prev_next_child);
419+
if let Some(prev_next_child) = prev_next_child {
420+
prev_next_child.reassign(DomSlot::at_end());
421+
}
416422

417423
nodes.trim_start_text_nodes();
418424

@@ -423,6 +429,10 @@ mod feat_hydration {
423429
};
424430

425431
node_ref.set(Some((*el).clone()));
432+
if let Some(prev_next_sibling) = prev_next_sibling {
433+
prev_next_sibling.reassign(DomSlot::at((*el).clone()));
434+
}
435+
*prev_next_sibling = None;
426436

427437
BTag {
428438
inner,

packages/yew/src/dom_bundle/btext.rs

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ mod feat_hydration {
9292
use web_sys::Node;
9393

9494
use super::*;
95-
use crate::dom_bundle::{Fragment, Hydratable};
95+
use crate::dom_bundle::{DynamicDomSlot, Fragment, Hydratable};
9696

9797
impl Hydratable for VText {
9898
fn hydrate(
@@ -101,9 +101,19 @@ mod feat_hydration {
101101
_parent_scope: &AnyScope,
102102
parent: &Element,
103103
fragment: &mut Fragment,
104+
previous_next_sibling: &mut Option<DynamicDomSlot>,
104105
) -> Self::Bundle {
105-
let next_sibling = if let Some(m) = fragment.front().cloned() {
106-
// better safe than sorry.
106+
let create_at = |next_sibling: Option<Node>, text: AttrValue| {
107+
// If there are multiple text nodes placed back-to-back in SSR, it may be parsed as
108+
// a single text node by browser, hence we need to add extra text
109+
// nodes here if the next node is not a text node. Similarly, the
110+
// value of the text node may be a combination of multiple VText
111+
// vnodes. So we always need to override their values.
112+
let text_node = document().create_text_node(text.as_ref());
113+
DomSlot::create(next_sibling).insert(parent, &text_node);
114+
BText { text, text_node }
115+
};
116+
let btext = if let Some(m) = fragment.front().cloned() {
107117
if m.node_type() == Node::TEXT_NODE {
108118
let m = m.unchecked_into::<TextNode>();
109119
// pop current node.
@@ -117,27 +127,21 @@ mod feat_hydration {
117127
// Please see the next comment for a detailed explanation.
118128
m.set_node_value(Some(self.text.as_ref()));
119129

120-
return BText {
130+
BText {
121131
text: self.text,
122132
text_node: m,
123-
};
133+
}
134+
} else {
135+
create_at(Some(m), self.text)
124136
}
125-
Some(m)
126137
} else {
127-
fragment.sibling_at_end().cloned()
138+
create_at(fragment.sibling_at_end().cloned(), self.text)
128139
};
129-
130-
// If there are multiple text nodes placed back-to-back in SSR, it may be parsed as a
131-
// single text node by browser, hence we need to add extra text nodes here
132-
// if the next node is not a text node. Similarly, the value of the text
133-
// node may be a combination of multiple VText vnodes. So we always need to
134-
// override their values.
135-
let text_node = document().create_text_node("");
136-
DomSlot::create(next_sibling).insert(parent, &text_node);
137-
BText {
138-
text: "".into(),
139-
text_node,
140+
if let Some(previous_next_sibling) = previous_next_sibling {
141+
previous_next_sibling.reassign(DomSlot::at(btext.text_node.clone().into()));
140142
}
143+
*previous_next_sibling = None;
144+
btext
141145
}
142146
}
143147
}

packages/yew/src/dom_bundle/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,9 @@ mod feat_hydration {
9494
parent: &Element,
9595
fragment: &mut Fragment,
9696
node: VNode,
97+
previous_next_sibling: &mut Option<DynamicDomSlot>,
9798
) -> Self {
98-
let bundle = node.hydrate(root, parent_scope, parent, fragment);
99+
let bundle = node.hydrate(root, parent_scope, parent, fragment, previous_next_sibling);
99100
Self(bundle)
100101
}
101102
}

0 commit comments

Comments
 (0)